iOS内存管理
在学习内存管理的时候,查阅了不少资料,零零散散的记录在有道云笔记中,在这里总结提炼一下,希望在方便自己查看的同时能帮助到大家。
1.引用计数
在引用计数架构下,每个对象都有个可以递增或递减的计数器,用以表示当前有多少个事物想令此对象继续存活下去。这在OC中叫做“保留计数(retain count)”,不过也可以叫做“引用计数”。如果想使某个对象继续存活,那就递增其引用计数;用完了之后,就递减其引用计数。计数变为0,就表示没人关注此对象了,于是就可以把它销毁。
MRC:在手动引用计数模式下,NSObject协议声明了下面三个方法用于操作计数器,retain,release,autorelease。
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
ARC:苹果的官方说明:
在OC中采用ARC机制,让编译器来进行内存管理。在新一代Apple LLVM编译器中设置ARC为有效状态,就无需再次键入retain或release代码,这在降低程序崩溃、内存泄露等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立即释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
虽然现在项目中推荐使用ARC,但是理解MRC是非常有用的。
2. 引用计数式内存管理的思考方式
让我们先忽略ARC,来考虑引用计数式内存管理的思考方式。
自己生成的对象,自己所持有 非自己生成的对象,自己也能持有 不再需要自己持有的对象时释放该对象 非自己持有的对象无法释放2.1 自己生产的对象,自己所持有
使用以下名称开头的方法名意味着自己生成的对象自己持有:
alloc new copy mutableCopy在OC中方法名能体现出内存管理语义,以上面这4个名称开头的方法名表示“生成的对象自己持有”,也可以说成“生成的对象归调用者所有”。“生成的对象自己持有”意味着:调用上述四种方法的那段代码要负责释放方法所返回的对象。
{ //调用[[NSObject alloc]init]方法会在堆上产生一个对象,并且没有被release和autorelease。这个堆上的对象目前的引用计数为1 //把这个对象的指针赋值给obj。赋值不会导致引用计数的变化,相当于通过obj可以拿到这个堆上的对象使用了。 id obj = [[NSObject alloc]init]; //这个代码段快要结束了,把这个堆上的对象释放掉,不然会产生内存泄漏。 [obj release]; }
2.2 非自己生成的对象,自己也能持有
用alloc/new/copy/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的所有者。
//取得非自己生产并持有的对象 //调用[NSMutableArray array]方法,会在堆上产生一个对象,这个对象被调用了autorelease方法,它会再下一次运行循环的时候被release。可以把这个对象想象成引用计数为0的对象(虽然是过一段时间才变为0),只有retain一下,才能持有这个对象。 id obj = [NSMutableArray array]; //取得的对象存在,但自己不持有对象 //NSMutableArray对象被赋给变量obj,但变量obj自己并不持有该对象。使用retain方法可以持有对象。 //取得非自己生产并持有的对象 id obj = [NSMutableArray array]; //取得的对象存在,但自己不持有对象 [obj retain]; //自己持有对象
通过retain方法,非自己生成的对象跟用alloc/new/copy/muableCopy方法生成并持有的对象一样,成为了自己所持有的。
2.3 不再需要自己持有的对象时释放该对象
自己持有的对象一旦不再需要,持有者有义务释放该对象。释放使用release方法。
//自己生产并持有对象 id obj = [[NSObject alloc]init]; //自己持有对象 [obj release]; //释放对象,指向对象的指针仍然被保留在变量obj中,貌似能够访问,但对象一经释放绝对不可访问。
如此,用alloc方法由自己生成并持有的对象就通过release方法释放了。
用alloc/new/copy/mutableCopy方法生产并持有的对象,或者用retain方法持有的对象,一旦不再需要,务必要用release方法进行释放。
如果要用某个方法生成对象,并将其返还给该方法的调用方,那么它的源代码又是怎样的呢?
- (id)allocObject { //自己生成并持有对象 id obj = [[NSObject alloc]init]; //自己持有对象 return obj; }
如上例所示,原封不动地返回用alloc方法生成并持有的对象,就能让调用方也持有该对象,请注意allocObject是符合命名规则的。
allocObject名称符合前文的命名规则,因此它与用alloc方法生成并持有的对象的情况完全相同,所以使用allocObject方法也就意味着“自己生成并持有对象”。
那么,调用[NSMutableArray array]方法使取得的对象存在,但自己不持有对象,又是如何实现的呢?根据命名规则,不能使用以alloc/new/copy/mutableCopy开头的方法名,因此使用object这个方法名。
-(id)object { id obj = [[NSObject alloc]init]; //自己持有对象 [obj autorelease]; //取得的对象存在,但自己不持有对象 return obj; }
上例中我们使用了autorelease方法。使用该方法,可以使取得的对象存在,但自己不持有对象。autorelease提供这样的功能,使对象在超出指定的生存范围时能够自动并正确地释放(调用release方法)。
autorelease是把对象注册到自动释放池,等pool结束时自动调用release方法。
使用NSMutableArray类的array类方法等可以取得谁都不持有的对象,这些方法都是通过autorelease而实现的。此外,根据命名规则,这些用来取得谁都不持有的对象的方法名不能以alloc/new/copy/mutableCopy开头,这点需要注意。
id obj1 = [obj0 object]; //取得的对象存在,但自己不持有 //当然,也能通过retain方法将调用autorelease方法取得的对象变为自己持有。
id obj1 = [obj0 object]; //取得的对象存在,但自己不持有 [obj1 retain]; //通过retain使得自己持有对象
2.4 非自己持有的对象无法释放
对于用alloc/new/copy/mutableCopy方法生成并持有的对象,或是用retain方法持有的对象,由于持有者是自己,所以在不需要该对象时需要将其释放。而由此以外所得到的对象绝对不能释放。倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。
//自己生成并持有对象 id obj = [[NSObject alloc]init]; //自己持有对象 [obj release]; //对象已释放 [obj release]; //释放之后再次释放已非自己持有的对象,应用崩溃
或者在“取得的对象存在,但自己不持有对象”时释放
id obj1 = [obj0 object]; //取得的对象存在,但自己不持有对象 [obj1 release]; //释放了非自己持有的对象,这肯定会导致应用程序崩溃
帮助理解:
使用与持有是两回事。你可以使用这个对象,但是你并不是它的所有者。比如有个对象的所有者已经对它进行了autorelease,你可以使用这个对象,但是除非你进行了retain,否则你不是它的所有者。
从所有权的角度考虑内存管理问题,就能很容易解释为什么需要在description方法中向返回的NSString对象发送autorelease消息:
因为该只是创建了一个NSString对象,但是并不想拥有该对象。创建NSString的对象只是为了返回一个结果,将NSString对象“交出去”而已。交出去后,别人爱拥有就拥有,不爱拥有就算了。
3. ARC
实际上“引用计数式类存管理”的本质部分在ARC中并没有改变。就像“自动引用计数”这个名称表示的那样,ARC只是自动地帮助我们处理“引用计数”的相关部分。在编译单位上,可设置ARC有效或无效,这一点便能佐证上述结论。比如对每个文件可选择使用或不使用ARC。
上述的内存管理思考方式在ARC中依然有效,只是在源代码的的书写上有些不同。到底有什么样的变化呢?首先要理解ARC中追加的所有权声明。
3.1所有权修饰符
OC编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。
所谓对象类型就是指向NSObject这样的OC类的指针,例如NSObject*。id类型用于隐藏对象类型的类名部分,相当于C语言中常用的“ void* ”。
ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。所有权修饰符一共有4种。
3.1.1 __strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。
id obj = [[NSObject alloc]init];
该obj变量实际上被附加了__strong所有权。相当于:
id __strong obj = [[NSObject alloc]init];
再看如下的代码段:
{ //ARC id __strong obj = [[NSObject alloc]init]; }
此源代码明确指定了C语言的变量的作用域。ARC无效时,该源代码可记述如下:
//ARC无效 { id obj = [[NSObject alloc]init]; [obj release]; }
如此源代码所示,附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。
如strong这个名称所示,__strong修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
3.1.2 __weak修饰符
有些时候会出现循环引用的问题:
{ Dog* dog1 = [[Dog alloc]init];//狗对象;dog1持有狗对象的强引用 Cat* cat1 = [[Cat alloc]init];//猫对象;cat1持有猫对象的强引用 [dog1 setObj:cat1];//狗对象里的成员变量obj_持有猫对象的强引用 [cat1 setObj:dog1];//猫对象里的成员变量obj_持有狗对象的强引用 //现在狗对象的持有者是dog1、猫对象的成员变量obj //现在猫对象的持有者是cat1、狗对象的成员变量obj } //因为dog1,cat1超出作用域,强引用失效,所以自动释放狗对象和猫对象,引用计数从2变为1 //此时两个对象中的成员互相引用两个对象,发生内存泄露
像下面这种情况,虽然只有一个对象,但在该对象持有其自身时,也会发生循环引用(自引用)。
id test = [[Test alloc]init]; [test setObject:test];
使用__weak修饰符可以避免循环引用。弱引用不能持有对象,对对象的引用计数没有影响。还是那句话,可以使用但不持有,持有者把它release了,你就不能使用了。把之前的成员变量的修饰符改成__weak即可解决循环引用问题。
__weak修饰符还有另一个优点。在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且被赋值为nil。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。
3.1.3 __unsafe_unretained修饰符
附有_unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样,因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放。到这里,__unsafe_unretained修饰符和__weak修饰符是一样的。
__unsafe_unretained修饰的变量与__weak有点区别是:在对象被废弃时,不会被赋值为nil。
3.1.4 __autorelease修饰符
用附有__autoreleasing修饰符的变量替代autorelease方法。
前面说到,用alloc/new/copy/mutableCopy以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的所有者。
我们自己编写不以这些开头的方法时,编译器会按照规则帮我们在要返回的对象上调用autorelease方法。
有种情况:
比如NSData中的一个方法:
- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask error:(NSError **)errorPtr;
我们需要声明一个NSError *error;
然后把&error
传入方法中。
这里的(NSError **)errorPtr
,其实等同于
(NSError * __autoreleasing *)errorPtr
这样就会把*error加入到自动释放池中。之所以这样做,是为了符合内存管理的思考方式,作为alloc/new/copy/mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成的对象。因此,使用附有__autoreleasing修饰符的变量作为对象取得参数,与除alloc/new/copy/mutableCopy外其他方法的返回值取得的对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。
上一篇: android内存管理
下一篇: 怎么合理的“关闭”二级域名网站?