自动引用计数
1.1什么是自动引用计数
自动引用计数(ARC, Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术。
1.2 内存管理/引用计数
1.2.1 概要
上班时办公室需要照明,此时灯应该是亮的,下班后无人需要照明,应该关灯。为了判断办公室是否需要照明,我们导入计数功能来计算“需要照明的人数”:
(1)第一个人进入办公室,“需要照明的人数”加1。计数值从0变成1,因此要开灯。
(2)之后每当有人进入办公室,“需要照明的人数”就加1。
(3)每当有人离开办公室,“需要照明的人数”就减1。
(4)当最后一个人离开办公室时,“需要照明的人数”由1变为0,无人需要照明,因此要关灯。
OC中对象的管理与中办公室照明设备的管理时一样的都使用了引入计数的功能。
对照明设备所做的动作 | 对OC对象所做的操作 |
---|---|
开灯 | 生成对象 |
需要照明 | 持有对象 |
不需要照明 | 释放对象 |
关灯 | 废弃对象 |
1.2.2 内存管理的思考方式
- 自己生成的对象,自己持有。
- 非自己生成的对象,自己也能持有。
- 不再需要自己持有对象的时释放。
- 非自己持有的对象无法释放。
对象操作 | OC方法 |
---|---|
生成并持有对象 | alloc/new/copy/mustableCopy等方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
Cocoa框架中Foundation框架类库中的NSObject类担负内存管理的职责(NSObject是OC中的基类)。
自己生成的对象,自己持有
// 自己生成并持有
-(instancetype)allocMyObject;
// 错误命名
-(instancetype)allocate;
非自己生成的对象,自己也能持有
类方法生成,retain方法持有。
不再需要自己持有对象的时释放
自己生成的对象release方法释放。
非自己生成的对象通过autorelease方法取得对象存在,但自己不持有对象。
autorelease方法功能:使对象在超出指定的生存范围时能够自动并正确释放(调用release方法)
非自己持有的对象无法释放
释放非自己持有的对象,程序会崩溃。
释放完对象,自己就不再持有该对象,再对该对象进行释放,程序会崩溃。
1.2.3 alloc/retain/release/dealloc 实现
1.2.4 苹果的实现
NSObject已开源https://opensource.apple.com/source/objc4/objc4-680/runtime/NSObject.mm.auto.html
GNUstep将引用计数保存在对象占用内存块头部的变量中,书中指出苹果大概就是采用散列表(引用计数表)来管理引用计数。
通过内存块头部管理引用计数的好处如下:
- 少量代码即可完成。
- 能够统一管理引用计数内存块与对象内存块。
通过引用计数表管理引用计数的好处如下:
- 对象用内存块的分配无需考虑内存块头部。
- 引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块。
1.2.5 autorelease
autorelease(自动释放)类似于C语言的自动变量(局部变量)的特性:超出作用域,对象会被释放掉。与C语言不同的是OC可以设定变量的作用域。
autorelease的具体方法如下:
(1) 生成并持有NSAutoreleasePool对象;
(2) 调用已分配对象的autorelease实例方法;
(3) 废弃NSAutoreleasePool对象。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSObject *obj = [[NSObject alloc] init];
[obj autorelease];
// 这里等同于 [obj release]
[pool drain];
1.2.6 autorelease的实现
1.2.6 苹果的实现
1.3 ARC规则
1.3.1 概要
1.3.2 内存管理的思考方式
ARC内存管理的思考方式和MRC相同,只是源代码记叙方法上稍有不同。
1.3.3 所有权修饰符
__strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符。
// 以下两种写法等同
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
__strong修饰符表示对对象的“强引用”。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
__weak修饰符
__weak是避免循环引用而引入的,表示对对象的“弱引用”。弱引用不能持有对象的实例,所以在超出其变量作用域时,对象即被释放掉。
__unsafe_unretained修饰符
__unsafe_unretained
是不安全的所有权修饰符。需要注意的是附有__unsafe_unretained
修饰符的变量不属于编译器的内存管理对象。赋值给附有__unsafe_unretained
修饰符变量的对象在通过该变量使用时,如果没有确保其确实存在,那么应用程序就会崩溃。
在iOS4下,由于还未支持__weak
,使用__unsafe_unretained
来替代__weak
。
__autoreleasing修饰符
在ARC有效时,用autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法。
“id *类型”默认为“id __autoreleasing *”类型
1.3.4 规则
ARC规则:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 须遵守内存管理的方法命名规则
- 不要显式调用dealloc
- 使用@autoreleasepool块替代NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员
- 显式转换“id”和“void *”
NSZone
NSZone是为防止内存碎片化而引入的结构,对内存的分配区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高内存管理的效率。(Zones are ignored on iOS and 64-bit runtime in macOS. You should not use zones in current development.)
*显式转换“id”和“void ”
/* ARC无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
/* ARC有效 */
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
__bridge
转换安全性比较低,如果管理时不注意赋值对象的所有者,很可能因悬垂指针而导致程序崩溃。为了补充__bridge
转换的不足,还有另外两种转换,分别是__bridge_retained
转换和__bridge_transfer
转换。
__bridge_retained
转换可使要转换赋值的变量也持有所赋值的对象。
__bridge_transfer
转换提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。
这些转换多数使用在Objective-C对象与Core Foundation对象之间的相互转换中。
1.3.5 属性
属性声明的属性 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained |
copy | __strong修饰符(但是赋值的是被复制的对象) |
retain | __strong修饰符 |
strong | __strong修饰符 |
unsafe_unretained |
__unsafe_unretained 修饰符 |
weak | __weak修饰符 |
1.3.6 数组
1.4 ARC的实现
1.4.1 __strong修饰符
{
id __strong obj = [[NSObject alloc] init];
}
该源代码可转换为调用以下函数
/* 编译器的模拟代码*/
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
变量作用域结束时,通过objc_release释放对象。
{
id __strong obj = [NSMutableArray array];
}
+ (id)array {
return [[NSMutableArray alloc] init];
}
转换为
/* 编译器的模拟代码 */
{
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
}
+ (id)array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
objc_retainAutoreleasedReturnValue
和objc_autoreleaseReturnValue
是成对的。
objc_retainAutoreleasedReturnValue
函数主要用于优化程序运行。它是用于自己持有(retain)对象的函数,但他持有的对象应为返回注册在autorelease中对象的方法,或是函数的返回值。
objc_autoreleaseReturnValue
用于alloc/new/copy/mutableCopy方法以外的类方法返回对象的实现上。
像该源代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue
函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturnValue
函数同objc_autorelease
函数不同,一般不仅限于注册对象到autoreleasepool中。
objc_autoreleaseReturnValue
函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用了objc_retainAutoreleasedReturnValue()
函数,那么就不讲返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue
和objc_retain
函数不同,它几遍不注册到autoreleasepool中二返回对象,也能够正确地获取对象。通过objc_retainAutoreleasedReturnValue
和objc_autoreleaseReturnValue
函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一定过程达到了最优化。
1.4.2 __weak修饰符
- 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量。
- 使用附有__weak修饰符的变量,机会使用注册带autoreleasepool中的对象。
{
// 假设变量obj附加__strong修饰符且对象被赋值
id __weak obj1 = obj;
}
可转化为
/* 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
通过objc_initWeak
函数初始化附有__weak修饰符的变量,在变量作用域结束时会通过objc_destroyWeak
函数释放该变量。
/* objc_initWeak函数将附有__weak修饰符的变量初始化为0后,
会将赋值的对象作为参数调用objc_storeWeak函数
*/
obj1 = 0;
objc_storeWeak(&obj1, obj);
// objc_destroyWeak函数将0作为参数调用objc_storeWeak函数
objc_storeWeak(&obj1, 0);
即前面的源代码与下列源代码相同
/* 编译器的模拟代码 */
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak
函数把第二参数的赋值对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则把变量的地址从weak表中删除。
对象被废弃动作如下:
- 从weak表中获取废弃对象的地址作为键值记录。
- 从包含在记录中附有__weak修饰符变量的地址,赋值为nil。
- 从weak表中删除该记录。
- 从引用计数表中删除废气对象的地址为键值的记录。
{
id __weak obj = [[NSObject alloc] init];
}
以上代码编译器会警告 :Assigning retained object to weak variable; object will be released after assignment。
/* 编译器的模拟代码 */
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);
虽然自己生成并持有的对象通过objc_initWeak
函数被赋值给附有__weak修饰符的变量中,但编译器判断其没有持有者,故该对象立即通过objc_release
函数被释放和废弃。这样一来nil就会被赋值给引用废弃对象的附有__weak修饰符的变量。
以上说明了若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
该源代码可转换为如下形式
/* 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
与被赋值时相比,在使用附有__weak
修饰符变量的情形下,增加了对objc_loadWeakRetained
函数和objc_autorelease
函数的调用。这些函数的动作如下:
(1)objc_loadWeakRetained
函数取出附有__weak
修饰符变量所引用的对象并retain。
(2)objc_autorelease
函数将对象注册到autoreleasepool中。
以上可知使用附有__weak修饰符的变量,机会使用注册带autoreleasepool中的对象。
1.4.3 __autoreleasing修饰符
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。
1.4.4 __引用计数
获取引用计数数值的函数:_objc_rootRetainCount(id obj)