iOS内存管理的思考方式:
(1)自己生成的对象,自己持有。
这里的“自己”固然指“对象的使用环境”,但将其理解为编程人员“自身”也是没错的。
使用由以下名称开头的方法意味着自己生成的对象只有自己持有(这些对象不会被注册到Auto Release Pool中):alloc,new,copy,mutableCopy。
eg:
<span style="white-space:pre"> </span>id obj = [Objective new];
<span style="white-space:pre"> </span>/*自己持有了obj对象*/
(2)非自己生成的对象,自己也能持有。
这是针对于通过(1)中那四个以外的方法来获得的对象。
eg:
<span style="font-size:10px;"> {
id __strong obj = [NSArray array];
/*自己持有了obj对象*/
}
/*出了作用域,强引用失效,所以自动地释放持有的对象*/</span>
注:“引用”是指定义的变量引用(或持有)了通过分配内存生成的对象。
(3)自己持有的对象不再需要时(由自己)释放。
(4)非自己持有的对象无法(由自己)释放。
对象的所有权
所有权对应于持有的说法。
1.__strong修饰符
strong 表示“强引用”,是默认的修饰符。strong修饰的变量在出了其作用域之后强引用失效,或者叫被废弃(即不再持有),ARC有效时,被废弃的对象会在适当的时候被系统释放,ARC无效时,若在变量作用域内不对其进行手动释放,则造成内存泄露。
一个变量本来强引用了一个对象A,若再对此变量赋值另一个对象B,则此变量不再持有A。一个对象被多个变量强引用,每把一个变量置为nil时,对象的持有者就减少一个,引用计数减1。
2.__weak/__unsafe_unretained修饰符
__strong修饰的变量互相引用或自身强引用时,会造成内存泄露,__weak修饰符正是为了解决这一问题而出现的,它避免了循环引用。
低版本系统中,__weak可用__unsafe_unretained替代。后者正如其名,是不安全的所有权修饰符。ARC是编译器的工作,但__unsafe_unretained修饰的变量不属于编译器的管理范围。结果就是,它同__weak一样,自己生成的对象不能继续被自己持有,所以生成的对象会立即被释放。
eg1:
id __weak obj = [[NSObject alloc] init];
NSLog(@"obj:%@", obj); //输出obj为null
eg2:
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;
}
/*
到这里obj0超出作用域被释放,obj1被__unsafe_unretained修饰,不能持有对象,故obj变成悬垂指针(指向已经被释放的内存)。
*/
__weak在实现中使用了一个weak表,是一种hash表结构,每个对象的地址作为表的查询键值,通过每个键值索引的是对应地址存储的对象的引用变量。对weak表的操作会消耗CPU资源,良策是只在需要避免循环引用时使用__weak修饰符。
__weak修饰的变量被废弃之后,变量会被赋值nil,其他修饰符无此功能。
__weak不增加引用计数,但在Auto Release Pool中会增加。
3.__autoreleasing修饰符
编译器会检查分配对象的方法名是否以alloc / new / copy / mutableCopy开头,这些方法是自己生成的对象自己持有,鼓不用注册到Auto Release Pool。不是则将返回的对象注册到Auto Release Pool中。
实际中一般不显式使用此修饰符,而是通过@sutoreleasepool来间接使用,当pool销毁时,会向注册到其中的所有对象发送release消息,在下一个runloop开始时会将引用计数为0的对象销毁。
隐式使用__autoreleasing的情况:
1)访问__weak修饰的变量时,实际上必定要访问注册到Auto Release Pool中的对象。这是为了避免__weak修饰的变量被立即释放(即保证在@autoreleasepool块结束之前不释放)。所以
<span style="font-size:10px;">id __weak obj1 = obj0;
NSLog(@"class=%@",[obj1 class]);</span>
与下面的代码相同:
<span style="font-size:10px;">id __weak obj1 = obj0;
id __autoreleaseing tmp = obj1;
NSLog(@"class=%@",[tmp class]);</span>
2)返回autoreleasing对象。如,以下代码是合法的:
<span style="font-size:10px;">+(id)array
{
return [[NSMutableArray alloc] init];
}</span>
因为返回的对象被注册到了Auto Release Pool中,故不会在函数返回之后释放。
3)指针类型
id *obj;等价于 id __autoreleasing *obj;
对象的指针NSObject **obj;等价于 NSObject *__autoreleasing *obj;
因此,以下代码会产生编译错误;
<span style="font-size:10px;">NSError *err = nil;
NSError **pErr = &err;</span>
因为赋值给对象指针式,对象所有权必须一致。下面的写法就会正常编译:
<span style="font-size:10px;">NSError *err = nil;
NSError *__strong *pErr = &err;</span>
当显示使用__autoreleasing时,不能与static一起使用。
ARC规则
对象不能作为C语言结构体的成员。因为ARC把内存管理工作分配给编译器,编译器必须要知道对象的生命周期,而把对象作为C语言结构体的成员,这在C标准上就是不能实现的,因为C语言主要靠变量作用域管理其生命周期,这与ARC机制无可比性。
所有权转换:非ARC环境下,id与void*可直接转换,ARC环境下,使用bridge转换。转换效果:__bridge与__unsafe_unretained类似,__bridge_retained与retain类似。__bridge_transfer与release类似,
属性与所有权修饰符的对应关系
只有copy不是简单的赋值,而是通过copyWithZone:方法复制了源对象,再将副本赋给新值。
引用计数
所有权修饰符的作用就是通过对引用计数值的影响来实现的。一次强引用会增加引用计数,变量被注册到Auto Release Pool也会增加引用计数值(即使变量被__weak修饰)。
取得引用计数的值不能用retain方法,这个方法不是给开发者使用的。可以使用_objc_rootRetainCount(id obj)函数。但实际上,这个方法获取的值也不能完全信任,比如,对于已释放的对象、不正确的对象地址,以及在多线程编程中的竞态条件问题。