iOS设备中某个应用内存使用超过单个进程上的限制,会被系统终止使用。
内存问题常出现在重复的内存释放
和循环引用
的情况。
内存消耗
指的是应用消耗的RAM
。iOS的虚拟内存模型并不包含交换内存,意味着不会被用来分页内存。
应用中内存消耗分为两部分:栈大小
和堆大小
。
应用中新线程都有专用的栈空间,该空间由保留的内存和初始提交的内存组成。栈可以在线程存在期间自由使用。线程的最大空间很小,这就决定了以下限制:
main() { //第一个栈帧
func1(); //第二个栈帧
}
func1() {
func2(); //在func1()上增加一个栈帧。
}
每个进程的所有线程共享同一个堆。应用并不能控制分配给它的堆。只有操作系统才能管理堆。
通过类创建的对象相关数据都存放在堆中。类可能包含属性或值类型的实例变量(iVars,基本数据类型),如int、char或struct。因为对象是在堆内创建的,所以他们只消耗内存。
使用NSString、载入图片、创建或使用JSON/XML数据、使用视图等都会消耗大量的堆内存。需要关注平均值和峰值内存使用的最小化。
当对象被创建并赋值时,数据可能会从栈复制到堆。类似的,当值仅在内部使用时,也可能会被从堆复制到栈。
虽然没有强制规定,但内存最好不要超过80%~85%,要给系统内核留下足够内存。**不要忽视didReceiveMemoryWarning
信号。
管理模型基于持有关系(引用计数?retain or release?)的概念。如果一个对象正处于被持有状态,那它占用的内存就不能被回收。
当一个对象创建于某个方法内部十,那么方法就持有该对象。如果这个方法返回,则调用者声称建立了持有关系。这个值可以付给其他变量,对应的变量同样会声称建立了持有关系。
一旦与某个对象相关的任务全部完成,就是放弃了持有关系。这一过程中没有转移持有关系,而是分别增加或减少了持有者的数量。当持有者的数量降为零时,对象会被释放,相关内存会被回收。这种持有关系计数通常被正式成为引用计数
。亲自管理时,被称为手动引用计数(MRC)
。现如今的应用大都使用自动引用计数(ARC)
。
引用计数基本结构
NSString *message = @"a"; //引用计数为1
NSString *messageRetained = [message retain]; //引用计数为2
[messageRetained release]; //引用计数为1
[message release]; //引用计数为0
NSLog(@"%@", message); //此时值已经为未定义状态,但还是能取得相同的值,因为对应的内存还没被回收或重置。
方法中的引用计数
//一个Person类的部分
- (NSString *)address {
NSString *result = [[NSString alloc] initWithFormat:@"%@", self.city]; //首次创建为1
return result;
}
- (void)showPerson:(Persson *)p {
NSString *paddress = [p address]; //引用计数仍未1
NSLog(@"%@", paddress); //不变
[paddress release]; //引用计数为0
}
自动释放对象让你能够放弃一个对象的持有关系,但是延后对它的销毁。当方法中创建一个对象并需要将其返回时,自动释放就显得十分有用。
- (NSString *)address{
NSString *result = [[[NSString alloc] initWithFortmat:@"%@", self.city] autorelease];
//[result release]; //3对象在返回之前释放,返回引用无效。
return result;
}
规则:
- 持有的对象是alloc方法生成并返回的;
- 确保没有内存泄露,必须在失去引用之前放弃关系;
- 如果使用release,那么对象释放将发生在返回之前,因而方法将返回无效的引用;
- autorelease表明想放弃持有关系,但同时发放的调用者允许对象被释放之前使用对象。
当创建一个对象并将其从非
alloc
方法返回时,应使用autorelease。
自动释放池块
是允许你放弃对一个对象的持有关系、但可避免它立即被回收的一个工具。当从方法返回对象时,这个功能非常有用。
它(自动释放池块)
能确保在块内创建的对象会在块完成时被回收(用完了就被回收)。这在创建了多个对象的场景中非常有用。本地的块可以用来今早的释放其中的对象,从而使内存用量保持在较低水平。
自动释放池块用@autoreleasepool
表示。
//例子
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
块中收到autorelease
消息的所有对象都会在autoreleasepool
块结束时收到release消息。更加重要的是,每个autorelease调用都会发送一个release消息。这意味着如果一个对象收到了不知一次的autorelease消息,那它也会多次收到release消息。这样能保证对象引用技术下降到使用autoreleasepool块之前的值。如果计数为0,则对象将被回收,从而保持较低的内存使用率。
autoreleasepool
可以嵌套。另外autorelease对象在autoreleasepool块内执行,能确保autorelease对象被释放,从而防止应用内存泄露的情况。
AppKit和UIKit框架将事件–循环的迭代放入autoreleasepool块中。因此,通常不需要自己在创建autoreleasepool块。
使用autoreleasepool块的场景:
-(void)threadStart {
@autoreleasepool{
//新线程的代码
}
}
//其他地方
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
}
出现的原因:持续跟踪retain、release和autorelease不容易。2011年提出的ARC。Objective-C同时支持ARC和MRC,Swift不支持MRC。
ARC
是编译器特性。评估了对象在代码中的生命周期,并在编译时自动注入合适的内存管理调用(ARC内存管理调用发生在编译时)。编译器还会生成合适的dealloc方法。
优势:节约了开发时间,降低了开发难度,减少了代码量。
禁用ARC,需要进入 Targets->Build Phases->Compile Sources,选择必须禁用 ARC的文件,然后添加编译器标记 -fno-objc-arc。
@selector()
)同样有效。因此,[obj release]
或@selector(retain)
是编译时的错误。dealloc
方法,但是不能主动调用。不仅不能调用其他对象的dealloc方法,也不能调用其超类。如:[super dealloc]
是编译时的错误。//won't work
@property NSString *newTitle;
//Works
@property (getter=getNewTitle) NSString *newTitle;
ARC带来了新的引用类型:弱引用
。支持的类型包括:
ARC
为变量提供了四种生命周期限定符。
//TypeName * qualifier variable;
Person * __strong p1 = [[Person alloc] init]; //1
Person * __weak p2 = [[Person alloc] init]; //2
Person * __unsafe_unretained p3 = [[Person alloc] init]; //3
Person * __autoreleasing p4 = [[Person alloc] init]; //4
1.创建后引用计数为1,并且对象在p1引用期间不会被回收。
2.创建对象后饮用计数为0,对象会被立即释放,且p2将置为nil。
3.创建后计数变为1,对象会被立即释放,但p3不会被设置为nil。
4.创建后计数为1,当方法返回时对象会被立即释放。
属性声明有六个持有关系限定符:
- strong,默认符,指定了__strong关系;
- weak,指定了__weak关系;
- assign,在新版本中,assign的含义发生了变化。在ARC之前,assign是默认的持有关系限定符。在启用ARC之后,assign表示了__unsafe_unretained关系;
- copy,暗指了__strong关系,暗示了setter中的复制语句的常规行为;
- retain,指定了__strong关系;
- unsafe_unretained,指定了__unsafe_unretained关系。
因为assign和unsafe_unretained只进行值复制而没有任何实质性的检查。