自动引用计数
自动引用计数(ARC, Automatic Reference Counting), 是指内存管理中对引用采用自动计数的计数,让编译器来进行内存管理
内存管理/引用计数
思考方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
这四种方式,对应成oc的方法的话,就是:
对象操作 | oc方法 |
---|---|
生成并持有对象 | alloc/new/copy/mutableCopy等方法 |
持有对象 | retain方法 |
释放对象 | release方法 |
废弃对象 | dealloc方法 |
自己生成的对象,自己持有
使用以下名称的方法意味着自己生成的对象只有自己持有:alloc
, new
, copy
, mutableCopy
. e.g.:
id obj = [[NSObject alloc] init];复制代码
NSObject
通过alloc
类方法自己生成并持有对象,并将指向此对象的指针赋值给变量obj(obj实际上是一个指针)
非自己生成的对象,自己也能持有
id obj = [NSMutableArray array];复制代码
上面这段代码中,变量obj并不持有NSMutableArray
生成的类对象(可以通过retain
方法进行持有)
不在需要自己持有的对象时释放
释放采用release
方法
id obj = [[NSObject alloc] init];[obj release]; // 释放obj指向的对象,obj本身仍存在,但是其指向的对象已经被释放复制代码
非自己持有的对象不能释放
alloc/retain/release/dealloc的实现
根据苹果的文档,可以看出alloc的实际实现是:
+ alloc+ allocWithZone:class_createInstancecalloc复制代码
autorelease
具体使用方法:
- 生成并持有
NSAutoreleasePool
对象 - 调用已分配对象的autorelease实例方法
- 废弃
NSAutoreleasePool
对象
对于所有调用过autorelease
实例方法的对象,在废弃NSAutoreleasePool
对象时,都将调用release
实例方法。需要注意的是,如果生成大量的autorelease
对象,而NSAutoreleasePool
对象又不废弃的话,大量对象得不到释放,就会出现内存不足的现象。
autorelease
的实现如下:
class AutoreleasePoolPage { static inline void *push () { // 相当于生成或持有NSAutoreleasePool类对象 } static inline void *pop() { // 相当于废弃NSAutoreleasePool类对象 releaseAll(); } static inline id autorelease(id obj) { // 相当于NSAutoreleasePool类的addObject类方法 AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例 autoreleasePoolPage->add(obj); } id *add(id obj) { // 将对象添加到内部数组 } void releaseAll() { 调用内部数组中对象的release实例方法 }}复制代码
从源代码中我们可以看到,autorelease
方法的实现是通过动态数组来进行自动释放池的对象的存储,在需要释放的时候废弃数组中的对象。
ARC规则
所有权修饰符
在ARC有效时,id类型的所有权修饰符有4种:__strong
, __weak
, __unsafe_unretained
和__autoreleasing
__strong
__strong
是默认的修饰符。表示对对象的强引用,在其作用域被废弃的时候,随着强引用的失效,引用的对象会随之释放
__weak
__weak
防止循环引用引起的内存泄漏。比方说
id obj1 = [[NSObject alloc] init];id obj2 = [[NSObject alloc] init];obj1.obj = obj2;obj2.obj = obj1;// orobj1.obj = obj;复制代码
上面代码中两个写法都会造成循环引用,因为两个对象之间有互相强引用,导致作用域被废弃时,两个对象之间得不到释放。通过__weak
可避免这种情况。除此之外,在持有某对象的弱引用时,如果对象被废弃的话,弱引用将自动失效并且处于nil
__unsafe_unretained
__unsafe_unretained
修饰的变量跟__strong
和__weak
不同,是不属于编译器的内存管理对象。其变量既不持有强引用也不持有弱引用。
__autoreleasing
在ARC有效时,使用@autoreleasepool
来替代NSAutoreleasePool
类,用__autoreleasing
替代autorelease
方法。一般不会显式附加。对于非自己生成当持有的对象来说,编译器会自动检查方法名是否以alloc/new/copy/mutableCopy
开头,如果不是的话会将对象注册到自动释放池当中。
使用__weak
变量的时候,必定会访问到注册到自动释放池中的对象。
因为__weak
只持有对象的弱引用,在访问引用对象的过程中,该对象随时有可能被废弃。但是只要将对象注册到自动释放池中的话,则能保证在作用域结束之前,对象一直存在。
可以使用_objc_autoreleasePoolPrint()
方法调试自动释放池上的对象
规则
- 不能使用
retain/release/retainCount/autorelease
: 内存管理是编译器的工作,没必要使用内存管理的方法 - 不能使用
NSAllocateObject/NSDeallocateObject
- 须遵守内存管理方法命名规则:
alloc/new/copy/mutableCopy
开头的方法在返回对象时,必须返回给调用方所应当持有的对象。 - 不要显式调用
dealloc
- 使用
@autoreleasepool
替代NSAutoreleasePool
- 不能使用
NSZone
: - 对象型变量不能作为C语言结构体的成员:即C语言中不能使用OC的对象
- 显式转换
id
和void *
: 使用__bridge_retained
和__bridge_transfer
(多用于OC对象和Core Foundation对象的转换)
数组
id __strong *array = nil;
id *类型
默认为id __autoreleasing * 类型
,因此需要显式指定为__strong
。但是,这仅保证了__strong
修饰的id类型变量被初始化为nil,并不保证id指针型变量被初始化为nil
ARC的实现
__strong
{ id __strong obj = [[NSObject alloc] init];}// 编译器的模拟代码id objc = objc_msgSend(NSObject, @selector(alloc));objc_msgSend(obj, @selector(init));objc_release(obj);{ id __strong obj = [NSMutableArray array];}// 编译器的模拟代码id objc = objc_msgSend(NSMutableArray, @selector(array));objc_retainAutoreleasedReturnValue(obj); // 用于最优化程序运行。用于自己持有对象的函数,但它返回的对象应为返回注册在自动释放池中的对象的方法或者是返回值objc_release(obj);复制代码
objc_retainAutoreleasedReturnValue
和objc_autoreleaseReturnValue
一般是配套使用的,通过这两个方法,可以获取到原本应注册到释放池中的对象,省略了注册和查找的步骤,实现优化处理。
__weak
- 若使用
__weak
的变量所引用的对象被废弃的话,则将nil赋值给该变量 - 使用
__weak
的变量,即是使用注册到autoreleasepool中的对象。
{ id __weak obj1 = obj; // obj是__strong修饰的值}// 编译器模拟代码id obj1;objc_initWeak(&obj1, obj); // 初始化后调用objc_storeWeak函数-> obj1 = 0; objc_storeWeak(&obj1, obj);objc_destoryWeak(&obj1);// 等价于id obj1;obj1 = 0;objc_storeWeak(&obj1, obj);objc_storeWeak(&obj1, 0);复制代码
objc_storeWeak
将对象的地址作为key,将__weak
修饰的变量地址作为value注册到weak表(一个哈希表,跟引用计数表相同)。
释放对象的步骤:
objc_release
- 因为引用计数为0, 所以执行
dealloc
_objc_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating
在最后一个步骤中,会执行以下动作:
- 从weak表中获取废弃对象的地址为key的记录
- 将包含在记录中的所有
__weak
修饰的变量地址赋值为nil - 从weak表中删除
- 从引用计数表中删除废弃对象地址为key的记录
由上可知,如果大量使用__weak
修饰的变量的话,会消耗相应的CPU资源
下面验证下__weak
修饰的变量即为自动释放池中的对象
{ id __weak obj1 = obj; NSLog(@"%@", obj1);}// 编译器模拟代码id obj1;objc_initWeak(&obj1, obj);id tmp = objc_loadWeakRetained(&obj1); // 取出__weak修饰符变量所引用的对象并retainobjc_autorelease(tmp); // 将对象注册到自动释放池NSLog(@"%@", tmp);objc_destoryWeak(&obj1);复制代码
__autoreleasing
@autoreleasepool { 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);复制代码
引用计数
使用_objc_rootRetainCount()
方法来获取引用计数的数值(CFGetRetainCount((__bridge CFTypeRef)(obj))
也可) 使用_objc_autoreleasePoolPrint()
可打印自动释放池中的引用对象的状态:
// 需要通过extern声明extern void _objc_autoreleasePoolPrint();extern uintptr_t _objc_rootRetainCount(id obj);int main(int argc, char * argv[]) { id obj = [[NSObject alloc] init]; id obj1 = obj; NSLog(@"%ld", _objc_rootRetainCount(obj)); @autoreleasepool { id obj = [[NSObject alloc] init]; _objc_autoreleasePoolPrint(); id __weak o = obj; NSLog(@"before using __weak: retain count = %d", _objc_rootRetainCount(obj)); NSLog(@"class = %@", [o class]); NSLog(@"after using __weak: retain count = %d", _objc_rootRetainCount(obj)); _objc_autoreleasePoolPrint(); } return 0;}复制代码
输出如下:
2017-05-03 00:18:20.415 ObjectiveCDemo[7740:740389] 2objc[7740]: ##############objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0objc[7740]: 0 releases pending.objc[7740]: [0x1] ................ PAGE (placeholder)objc[7740]: [0x1] ################ POOL (placeholder)objc[7740]: ##############2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] before using __weak: retain count = 12017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] class = NSObject2017-05-03 00:18:20.418 ObjectiveCDemo[7740:740389] after using __weak: retain count = 1objc[7740]: ##############objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0objc[7740]: 1 releases pending.objc[7740]: [0x7f82e2003000] ................ PAGE (hot) (cold)objc[7740]: [0x7f82e2003038] ################ POOL 0x7f82e2003038objc[7740]: ##############复制代码