iOS-ARC
本文的内容包括
一、所有权修饰符
二、ARC的基本规则
三、ARC的实现
ARC中仍然是通过引用计数来管理内存,这个本质没有变。只是,不需要我们手动的写代码去管理内存了,编译器自动帮助我们管理“引用计数”相关的部分。那么ARC是通过什么方式来帮我们管理内存的呢?
ARC是通过编译期和运行期两部分来处理的:
- 编译期,编译器不是通过添加retain/release/autorelease这些方法,而是会直接调用更底层的C语言函数(如objc_retain)。
- 运行期,ARC也包含运行期组件。比如,某些类方法返回对象前,为其执行了autorelease操作,而大多数情况,我们会对返回的对象保留,比如:
_myPerson = [Person personWithName:@"Kobe"];
那么其实就相当于先执行了一个autorelease,然后又retain了一下。ARC可以在运行期检测到这一多余的操作,也就是autorelease后紧跟retain。那么会它们两个会被改为调用objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
。objc_autoreleaseReturnValue
会检查后边是否紧接着调用objc_retainAutoreleasedReturnValue
,如果是,就不将返回的对象注册到autoreleasepool中而直接传递,省略了autorelasepool注册,实现了最优化。
一、所有权修饰符
1. __strong修饰符
如它的名字一样,__strong表示对对象的强引用。持有强引用的变量在超出其作用域的时候被废弃,随着强引用的失效,引用的对象会随之释放。
知识点:
- __strong修饰符是id类型和对象类型默认的所有权修饰符,所以一般不写__strong。
- __strong和__weak和 __autoreleasing修饰符一样,可以将附有这些修饰符的自动变量初始化为nil。
2. __weak(iOS5以上才有,之前用__unsafe_unretained)
如它的名字一样,__weak修饰符表示对对象的弱引用。不持有对象。
知识点:
- 可以避免循环引用。
- 变量指向的对象被销毁了,变量也会自动置空为nil。
3. __unsafe_unretained
如它的名字一样,它是不安全的所有权修饰符。
知识点:
- 和__weak一样表示弱引用,不持有对象,但不会置nil,这也正是不安全的原因。
id __unsafe_unretained obj1 = nil;
{
id obj0 = [[NSObject alloc] init];
obj1 = obj0;
NSLog(@"A:%@",obj1);
}
NSLog(@"B:%@",obj1);
输出A和B虽然是一样的地址,但是此时B处obj1已经是野指针了。
4. __autoreleasing
通过给对象附加__autoreleasing修饰符 来替代调用autorelease方法,把对象注册到autoreleasepool中。
具体使用情况,在这里。
二、规则
1. 不能使用retain/release/retainCount/autorelease
2. 用@autoreleasepool{}代替NSAutoreleasePool对象
3. 不要显式的调用dealloc
多数情况下在dealloc中删除已注册的代理或观察者。不用书写[super dealloc],因为ARC已经自动处理了。
4. 必须遵守内存管理的方法命名规则
- alloc/new/copy/mutableCopy,以上述名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。这在ARC下,依然没有变。
- ARC下追加了一条命名规则:
- init,以init开头的方法,必须是实例方法,并且返回对象,类型应为id或该类的的对象类型,抑或是超类或子类型。该方法基本上只是对alloc方法返回的对象进行初始化操作并返回。
注:initialize方法并不包含在上述命名规则里。
5. 对象类型不能作为结构体的成员
因为ARC把内存管理的工作分配给了编译器,所以编译器必须能够知道并管理对象的生命周期。例如C语言的自动变量(局部变量)可以使用该变量的作用域来管理。但是对于结构体成员来说,是无法实现的。
6. 显式转换id和void*
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
但是,__bridge安全性与__unsafe_unretained类似,甚至更低。很容易造成野指针导致崩溃。
__bridge还有另外两种转换,__bridge_retained和__bridge_transfer
- Core Foundation 对象类型不在 ARC 管理范畴内,需要自己管理.
- __bridge只做类型转换,但是不修改对象(内存)管理权,原来是ARC管理的还用ARC,原来MRC管理的继续用MRC
- __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象;
- __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。
5. 不能使用NSAllocateObject/NSDeallocObject
6. 不能使用区域(NSZone)
三、ARC的实现
1. __strong实现
赋值给附有__strong修饰符的变量在实际的程序中是怎样运行的呢?
{
id __strong obj = [[NSObject alloc] init];
}
编译器在超出作用域时自动插入了release。
{
id __strong obj = [NSMutableArray array];
}
执行,alloc/new/copy/mutableCopy之外的方法,如array类方法:
像代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue,如果其后紧接着调用objc_retainAutoreleasedReturnValue(),那么就不将返回的对象注册的autoreleasepool中而直接传递。通过这两个方法,优化了程序运行。
2.__weak的实现
id __strong obj = [[NSObject alloc]init];
id __weak obj1 = obj;
objc_initWeak函数中的weak_register_no_lock()把赋值对象obj的地址作为键值,通过哈希查找找到weak弱引用表中对应的数组,将附有__weak修饰符变量的指针添加到数组中。如果没有找到数组,表示是第一个weak指针,则新建一个数组。
对象在被废弃时dealloc方法中会调用object_dispose函数,该方法内部会通过调用weak_clear_no_lock()
- 通过哈希查找从weak表中获取废弃对象地址作为键值的记录是一个数组。
- 将包含在数组中的所有附有__weak修饰符的变量地址,遍历数组赋值为nil。
- 从weak表中删除该记录。
- 从weak表中删除该键值。
所以,如果大量使用__weak修饰符的变量,则会消耗相应的cpu资源。良策是只在需要避免循环引用的时候使用__weak。
3. __autoreleasing修饰符的实现
objc_autorelease
4. 引用计数
可以通过_objc_rootRetainCount(obj)来获取对象的引用计数值。但实际上并不能完全信任该函数取得的值。对于已经释放的对象以及不正确的对象地址,有时也返回1。另外,在多线程中使用它,因为存在竞态条件的问题,所以取得的数值也不一定完全可信。当然它在调试中还是比较有用的。