内存管理:自动引用计数(ARC)
- 1. 引用计数式的内存管理
-
- 1.1 内存管理方法
- 1.2 引用计数的内部实现
- 1.3 autorelease 的使用与实现
-
- 1.3.1 使用方法
- 1.3.2 内部实现
- 2. ARC 规则
-
- 2.1 所有权修饰符
-
- 2.1.1 __strong 修饰符
- 2.1.2 __weak 修饰符
- 2.1.3 __autoreleasing 修饰符
-
- 隐式附加的情形
-
- 1. alloc/new/copy/mutableCopy 方法群
- 2. 访问 __weak 变量
- 3. 对象指针
- 2.1.4 __unsafe_unretained 修饰符
- 2.2 编码规则
- 2.3 属性
- 3. ARC 的实现
-
- 3.1 __strong 修饰符
- 3.2 __weak 修饰符
-
- 3.2.1 __weak 变量引用的对象被废弃后,将 `nil` 赋给该变量
- 3.2.2 使用 __weak 变量,即是使用注册到 `autoreleasepool` 中的对象
- 3.3 __autoreleasing 修饰符
自动引用计数(ARC,Automatic Reference Counting)指内存管理中对引用采用自动计数的技术。在 Objective-C 采用 ARC 机制后,编译器将自动进行内存管理,无需再次键入 retain 或 release 代码,可以降低程序崩溃、内存泄露的风险并减少代码量。
1. 引用计数式的内存管理
1.1 内存管理方法
引用计数式内存管理的四个原则:
- 自己生成的对象,自己所持有。
- 非自己生成的对象,自己也能持有。
- 不再需要自己持有的对象时释放。
- 非自己持有的对象无法释放。
与 Objective-C 方法对应:
对象操作 | Objecitve-C 方法 |
---|---|
生成并持有对象 | 以 alloc/new/copy/mutableCopy 开头的方法(驼峰命名) |
持有对象 | retain 方法 |
释放对象 | release 方法 |
废弃对象 | dealloc 方法 |
这些方法包含 NSObject 类中,使用以上方法进行内存管理:
//自己生成并持有对象
id obj = [[NSObject alloc] init];
// 取得非自己生成的对象,且自己不持有对象
id obj = [NSMutableArray array];
// 自己持有对象
[obj retain];
// 释放对象
[obj release];
用某个方法生成对象,并将其返回给调用方:
- (id) object{
id obj = [[NSObject alloc] init]; // 自己生成并持有对象
[obj autorelease]; // 将该对象注册到 releasepool 中
return obj;
}
使用
autorelease
方法可以使对象在超出指定的生存范围时能够自动并正确的释放。
原理:将对象注册到
autoreleasePool
中,当 pool 废弃时会自动调用
release
释放 pool 中的所有对象。
1.2 引用计数的内部实现
系统在运行时维护一张散列表(引用计数表)来管理引用计数。表的 key 值为内存块地址的散列值,value 为该对象的引用计数。
- 生成对象:在散列表中创建新的一行,记录新对象的引用计数。
- 持有对象:查找对象对应的行,该行的值 + 1。
- 释放对象:查找对象对应的行,该行的值 - 1。如果该行的值已经为 1,则销毁对象。
好处:
- 对象用内存块的分配无需考虑内存块头部。
- 引用计数表各记录中存有内存块地址,可从各个记录追溯到哥对象的内存块。即使出现故障导致对象的内存块损坏,只要引用计数表没有被破坏,依然可以找到内存块的位置。
1.3 autorelease 的使用与实现
1.3.1 使用方法
- 生成并持有
对象;NSAutoreleasePool
- 调用已分配对象的
实例方法;autorelease
- 废弃
对象。NSAutoreleasePool
对于所有调用过
autorelease
的对象,在废弃
NSAutoreleasePool
对象时,都将调用
release
方法。
1.3.2 内部实现
[obj autorelease]
本质上是调用
[NSAutoreleasePool addObject: obj]
。
autoreleasePool
会通过类似数组的数据结构持有对象。
autoreleasePool
的组织形式可以类比为函数调用栈的形式。系统内部由名为
AutorelesePoolPage
的类来维护
autoreleasePool
的栈。当新建一个
autoreleasePool
时,相当于入栈;废弃一个
autoreleasePool
时,会进行出栈。
2. ARC 规则
ARC 的本质部分是与 引用计数式内存管理一样的,只是 ARC 自动地帮助我们处理“引用计数”的相关部分。
2.1 所有权修饰符
Objective-C 中的对象类型必须附加所有权修饰符。
2.1.1 __strong 修饰符
__strong
是对象类型的默认修饰符。
附有
__strong
修饰符的变量在超出其作用域时,会释放其被赋予的对象。
__strong
修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
id __strong obj = [[NSObject alloc] init];
// ARC 无效
{
id obj = [[NSObject alloc] init];
...
[obj release];
}
// 示例
id __strong obj1 = [[NSObject alloc] init]; // obj1 持有 对象 A
id __strong obj2 = [[NSObject alloc] init]; // obj2 持有 对象 B
obj1 = obj2; // obj1 持有对象 B,对象 A 没有所有者被废弃。
强引用的对象变量在赋值时会持有对象,通过
__strong
修饰符,可以不必再使用
retain
和
release
方法。
另外,
__strong
、
__weak
、
__autoreleasing
修饰符会保证附有这些修饰符的自动变量初始化为
nil
。
2.1.2 __weak 修饰符
引用计数式管理中会发生“循环引用”的问题,引起内存泄漏,即应当废弃的对象在超出其生存周期后继续存在。
使用
__weak
修饰符可以避免循环引用,
__weak
修饰符提供弱引用,不能持有对象实例。
在持有某对象的弱引用时,若该对象被废弃,则此弱引用将自动失效且赋值为
nil
。通过检查
__weak
的变量可以判断被赋值的对象是否已被废弃。
2.1.3 __autoreleasing 修饰符
ARC无效时,使用
autorelease
:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
使用ARC,该代码写成:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
即指定“
@autoreleasingpool
块”来替代“
NSAutoreleasePool
对象生存、持有以及废弃”这一范围。
ARC中,通过将对象赋值给附加了
__autoreleasing
修饰符的变量来替代调用
autorelease
方法,即将对象被注册到
autoreleasepool
。
隐式附加的情形
通常,很少会需要显式地附加
__autoreleasing
。因为编译器在一些情形下会隐式附加
__autoreleasing
修饰符。
1. alloc/new/copy/mutableCopy 方法群
编译器会检查方法名是否以
alloc/new/copy/mutableCopy
开始,如果不是则自动将返回值的对象注册到
autoreleasepool
。(
init
方法的返回值不会注册到
autoreleasepool
)
对象所有权状况:
@autoreleasepool {
id __strong obj = [NSMutableArray array];
// obj 为强引用,自己持有对象。 并且该对象由编译器判断其方法名后,自动注册到 autoreleasepool
}
// obj 超出作用域,强引用失效,自动释放自己持有的对象。
// @autoreleasepool 块结束,注册到 pool 中的所有对象被自动释放。
// 对象没有所有者,废弃对象
2. 访问 __weak 变量
在访问附有
__weak
修饰符的变量时,实际上是访问注册到
autoreleasepool
的对象。
因为
__weak
只持有对象的弱引用,而在访问的过程中,该对象有可能被废弃,所以把要访问的对象注册到
autoreleasepool
中,在
@autoreleasepool
块结束前都能确保该对象存在。
3. 对象指针
对象的指针在没有显示指定时会自动附上
__autoreleasing
如
id *obj
/
NSObject **obj
会自动附加
__autoreleasing
变成
id __autoreleasing *obj
/
NSObject * __autoreleasing *obj
。
赋值给对象指针时,所有权修饰符必须一致。
在使用参数取得对象时,将参数声明为附有
__autoreleasing
修饰符的对象指针类型。
另外,在显式指定
__autoreleasing
修饰符时,对象必须要是自动变量(包括局部变量、函数以及方法参数)。
的调试用函数
autoreleasepool
,利用这一函数可调试注册到
_objc_autoreleasePoolPrint()
上的对象。
autoreleasepool
2.1.4 __unsafe_unretained 修饰符
附有
__unsafe_unretained
修饰符的变量不属于编译器的内存管理对象。
主要用于 iOS4 及 OS X Snow Leopard 的应用程序中。
在使用
__unsafe_unretained
修饰符时,赋值给
__strong
变量时需要确保被赋值的对象确实存在。
2.2 编码规则
在 ARC 有效的情况下必须遵守一定的规则:
- 不能使用 retain/release/retainCount/autorelease
- 不能使用 NSAllocateObject/NSDeallocateObject
-
须遵守内存管理方法的命名规则
追加了
的命名规则,该方法必须是实例方法,并且必须要返回该方法声明类的对象类型或超类或子类,该返回对象并不注册到 autoreleasepool 上。init
- 不要显式地调用 dealloc
- 使用 @autoreleasepoll 块替代 NSAutoreleasePool
- 不能使用 NSZone
-
对象型变量不能作为 C 语言结构体的成员
可强制转换为
或附加void *
修饰符,但要注意内存管理。__unsafe_unretained
- 显式转换
和id
使用void *
能够互相转换,多使用在 Objective-C 对象与 Core Foundation 对象之间的互相转换中。__bridge
2.3 属性
属性声明的属性与所有权修饰符的对应关系
属性声明 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained |
copy | __strong(但是赋值的是被复制的对象) |
retain | __strong |
strong | __strong |
unsafe_unretained | unsafe_unretained |
weak | __weak |
3. ARC 的实现
3.1 __strong 修饰符
赋值给
__strong
修饰符的变量在编译后转换为以下代码:
id __strong obj = [[NSObject alloc] init];
// 编译器的模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
在调用
alloc/new/copy/mutableCopy
以外的方法时,编译器会插入
objc_retainAutoreleasedReturnValue(obj)
函数,用于持有从方法中返回的对象:
id __strong obj = [NSMutableArray array];
// 编译器的模拟代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
与之相对应的函数是
objc_autoreleaseReturnValue
,用于
alloc/new/copy/mutableCopy
以外的返回对象的方法中。即使用
objc_autoreleaseReturnValue(obj)
函数返回注册到
autoreleasepool
中的对象。
+ (id)array {
return [[NSMutableArray alloc] init];
}
// 编译器的模拟代码
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
objc_autoreleaseReturnValue(obj)
函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用
objc_retainAutoreleasedReturnValue(obj)
,那么就不将返回的对象注册到
autoreleasepool
中,而是直接传递给方法或函数的调用方。
通过这两个函数的协作,可以不将对象注册到
autoreleasepool
中而直接传递,优化性能。
3.2 __weak 修饰符
__weak
修饰符主要提供两种功能,以下解析这两种功能的实现。
3.2.1 __weak 变量引用的对象被废弃后,将 nil
赋给该变量
nil
编译器对
__weak
变量赋值的处理:
id __weak weakObj = obj;
// 编译器的模拟代码
id weakObj;
objc_initWeak(&weakObj, obj);
objc_destroyWeak(&weakObj);
通过
objc_initWeak
函数初始化
__weak
变量,在变量作用域结束时通过
objc_destroyWeak
函数释放该变量。
objc_initWeak
函数调用下列代码,将
__weak
变量初始化为 0 后,将赋值的对象作为参数调用
objc_storeWeak
函数。
weakObj = 0;
objc_storeWeak(&weakObj, obj);
objc_destroyWeak
函数将 0 作为参数调用
objc_storeWeak
函数。
objc_storeWeak
函数把传入第二个参数的对象地址作为
key
,将第一个参数即
__weak
变量的地址作为
value
存入 weak 表 中。如果第二个参数为 0,则将该地址从 weak 表 中删除。
weak表与引用计数表相同,都是散列表。
使用 weak 表,将已废弃对象的地址作为
key
值进行检索,即可高速获取对应的
__weak
变量的地址。
另外,由于一个对象可同时赋值给多个
__weak
变量,所以同一个
key
值可注册多个
__weak
变量的地址。
释放对象时的动作
- objc_release
- 引用计数为 0,执行 dealloc
- _objc_rootDealloc
- object_dispose
- objc_destrcutInstance
- objc_clear_deallocating
对象被废弃时最后会调用
objc_clear_deallocating
函数,逻辑如下:
- 从 weak 表 中以废弃对象的地址为
检索对应的key
;value
- 将检索到的所有
即value
变量的地址,赋值为__weak
;nil
- 从 weak 表 中删除该记录;
- 从引用计数表中以废弃对象的地址为
,删除对应记录。key
通过以上逻辑即可实现
__weak
变量引用的对象被废弃后,将
nil
赋给该变量。
因此,如果大量使用
__weak
变量,会消耗 CPU 资源。所以尽量只在 需要避免循环引用时使用
__weak
修饰符。
3.2.2 使用 __weak 变量,即是使用注册到 autoreleasepool
中的对象
autoreleasepool
编译器将转换下面代码:
id __weak weakObj = obj;
NSLog(@"%@", weakObj);
// 编译器的模拟代码
id weakObj;
objc_initWeak(&weakObj,obj);
id tmp = objc_loadWeakRetained(&weakObj);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&weakObj);
-
:取出objc_loadWeakRetained
变量引用的对象并__weak
;retain
-
:将对象注册到objc_autorelease
;autoreleasepool
通过以上逻辑,
__weak
变量所引用的对象就被注册到
autoreleasepool
中,所以在
@autoreleasepool
块结束前都可以放心使用。
但是,如果大量使用
__weak
变量,注册到
autoreleasepool
中的对象也会大量增加。因此,在使用
__weak
变量时,最好先暂时赋值给
__strong
修饰符的变量后再使用。
id __weak o = obj;
NSLog(@"1 %@", o);
NSLog(@"2 %@", o);
NSLog(@"3 %@", o);
//上述代码会将对象在 autoreleasepool 中注册 5 次。
id tmp = o;
NSLog(@"1 %@", tmp);
NSLog(@"2 %@", tmp);
NSLog(@"3 %@", tmp);
// 先赋给 __strong 变量,此时仅注册了 1 次。
存在一些不支持 __weak 修饰的类,这些类重写了 retain/release 方法并实现独自的引用计数机制,若是将这些类对象赋值给 __weak 变量,一旦编译器检验出来会报出编译错误,且不支持 __weak 的类极为罕见。
当 allowWeakReference / retainWeakReference 返回 NO 时,也不能使用 __weak 修饰符。
3.3 __autoreleasing 修饰符
将对象赋值给
__autoreleasing
变量等同于 ARC 无效时调用对象的
autorelease
方法。
id __autoreleasing obj = [[NSObject alloc] init];
// 编译器的模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
对于
alloc/new/copy/mutableCopy
方法群之外的方法,改用
objc_retainAutoreleasedReturnValue
函数持有对象。