引用计数式内存管理
引用计数
iOS通过引用计数管理对象的生命周期,每个对象有其引用计数。
对象被强引用时引用计数加1,强引用解除时减1,引用计数为0时废弃对象。
引用计数为0时废弃对象的实现原理:iOS通过引用计数表(散列表)来管理引用计数:计数表中以内存块为键值,引用计数为对应记录。由于引用计数表的记录中存有内存块地址,所以可以追溯到对应对象的内存位置。
四个内存管理法则
- 自己生成的对象自己持有
- 非自己生成的对象,自己也可以持有
- 不再需要持有对象时释放对象
- 无法释放非自己持有的对象
Objectice-C对应方法 | retainCount | 对象操作
------ | :----- :| :------
alloc/new/copy/mutableCopy | +1 | 生成并持有对象
retain | +1 | 持有对象
release| -1 | 释放对象
dealloc | - | 废弃对象
MRC下,根据这四个法则,开发人员手动调用NSObject有关内存管理的方法,通过操作减引用计数来管理内存。
autorelease
将对象加入autorelease pool之后,废弃autorelease pool的时候对象的引用计数会-1 。
为什么要有autorelease
延迟内存的释放,即延长对象的生命周期,并且在合理的时机释放。比如返回值为指向对象的指针时:
- (NSObject *)initObject() {
NSObject *obj = [[NSObject alloc] init];
return obj;
}
initObject函数的调用者期望得到一个object,但由于obj在返回之后超出作用域会自动释放,这样调用者只能得到一个悬垂指针。在这种情况下,可以通过将obj对象加入autorelease pool来解决问题。
两种返回指针:返回指针有两种情况,官方进行了分类并给出了对指针拥有者的处理办法
- retained return value: 调用者拥有返回值 ,负责释放,如
alloc/copy/mutableCopy/new
打头的方法。自定义的方法也应该遵守这样的命名规则。 - unretained return value: 调用者不拥有,无需释放,如
[NSString stringWithFormat:]
autorelease原理与释放时机
autoreleasePool实际是由若干个autoreleasePoolPage以双向链表的方式形成的。
- next指针指向下一个加入autoreleasePool对象的位置
- 一个page占满了之后会新开一个page并与上一个连接
- 释放时机:在当前的runLoop开始和结束时,系统加入了AutoreleasePool的创建(objc_autoreleasePoolPush)和释放(objc_autoreleasePoolPop),每次push的时候会在当前的AutoreleasePoolPage的next位置添加一个值为零的哨兵对象,之后作为objc_autoreleasePoolPop(哨兵对象)的入参,哨兵对象之后的对象即为要释放的对象。
ARC的出现
无论是MRC还是ARC,都是围绕引用计数来进行引用计数式内存管理。ARC下,只是将对引用计数的处理工作交给了编译器。
所有权修饰符
为了让编译器能够正确的接手内存管理的工作,ARC下引入了id类型和对象类型的所有权修饰符。
__ strong:
默认修饰符,表示强引用。修饰的变量在被废弃(变量超出作用域/成员变量所属对象被废弃/变量赋值nil)时,会释放被赋予的对象。
__ weak:
弱引用,用于解决循环引用问题。弱引用不会持有对象,当对象被废弃,弱引用会自动失效且被赋值nil。
- weak引用能被赋值为nil的原因:系统实现了一个weak表(散列表),通过键值对的方式存储对象的地址与对应的__ weak引用变量的地址。对象被废弃时, 以废弃对象地址为键值查询找到其所有的weak引用并附为nil,然后删除记录。
- 使用__ weak修饰的变量时,对应对象会被注册到autoreleasepool中::以防访问过程中对象被释放。
__ unsafe_unretained:
与weak很像,不持有对象,从用法上看,是weak的不安全版本。
- 为什么不安全:此修饰符修饰的变量编译器实际上并不进行内存管理:当对象被废弃时,对应的被此修饰符修饰的变量并不会被置为nil,此时变量指向的内存内容是不确定的:如果这块内存没有被改写,代码可以正常运行;如果这块内存被部分改写,可能会出现很奇怪的 crash;如果这块内存正好被一个新对象覆盖,则会出现 unrecognized selector exceptions。
- 为什么有了__weak还要用 __ unsafe_unretained:(1)历史版本(如iOS4及其之前)不支持__ weak(2)当对象有大量weak引用时,对weak引用的nil赋值会消耗CPU资源,对性能有一定损耗。
__ autoreleasing:
ARC下,不能使用autorelease方法,也不能使用NSAutoReleasePool类,但通过新的语法,autorelease功能仍起作用。
/*ARC无效*/
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
变成了
/*ARC有效*/
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
_ _autoreleasing 的显式使用并不常见,且修饰的对象必须为自动变量。更多的是,__autoreleasing的隐式使用:
1. retained return value类型的指针返回时:
+ (id)array
{
return [[NSMutableArray alloc] init];
}
/*编译器模拟代码*/
+ (id) array
{
id obj = obj_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj); // 返回注册到自动释放池的对象
}
{
id __strong obj = [NSmutableArray array];
}
/*编译器模拟代码*/
{
id obj = obj_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleaseReturnValue(obj); // 由于引用retain
obj_release(obj);
}
此处可能出现runtime的优化:objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果之后紧接着调用retainAutoreleaseReturnValue,就不将对象注册到自动释放池,而是直接交付给retainAutoreleaseReturnValue。
2. 访问__weak修饰的变量时:
{
id __weak obj1 = obj0;
NSLog(@"obj1's Class:%@",[obj1 class]);
}
/*编译器模拟代码*/
{
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"obj1's Class:%@",[tmp class]);
}
*3. id 或对象指针没有显示指定修饰符时编译器自动添加__autoreleasing:
!!!有些方法隐式的使用autoreleasePool如:
- (void) loopThroughDictionary: (NSDictionary *)dic, error: (NSError **) error {
[dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, Bool *stop){
if(there is some error){
error = [NSError errorWithDomain:@"MyError" code:1 userinfo: nil];
}
}]
}
实际上是
- (void) loopThroughDictionary: (NSDictionary *)dic, error: (NSError **) error {
[dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, Bool *stop){
@ autoreleasepool{ // 隐式创建
if(there is some error){
error = [NSError errorWithDomain:@"MyError" code:1 userinfo: nil];
}
}
}]
}
由于error参数默认__autoreleasing修饰,在第一次迭代结束后error就被释放了,正确的写法:
- (void) loopThroughDictionary: (NSDictionary *)dic, error: (NSError **) error {
NSError *__block tempError; //加__block保证能在block内被修改
[dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, Bool *stop){
if(there is some error){
*tempError = [NSError errorWithDomain:@"MyError" code:1 userinfo: nil];
}
}]
if(error != nil){
error = tmpError;
}
}
或是参数显示强引用?(待验证)
属性标示符
- assign:对应到__unsafe_unretained, 表明setter仅做赋值,不增加对象的引用计数,用于基本数据类型
- strong:对应到__strong,赋值时先对值retain,再对旧值release,最后赋值。ARC模式下对象属性的默认值
- weak:对应到__weak
- unsafe_unretained:对应到__unsafe_unretained, 用于历史版本。ARC模式下非对象属性的默认值
- copy:对应到__strong,但是赋值操作比较特殊:赋值时进行copy而非retain操作,原来的值可变则深拷贝,不可变则浅拷贝
参考书目与文章
《Objective-C高级编程:iOS与OS X多线程和内存管理》
黑幕背后的Autorelease