引言:
我们都知道Objective-C通过“引用计数”来管理对象释放。基本原理就是管理对象的持有者个数(引用计数),引用计数为0时释放对象。现在有ARC(自动引用计数),则无需我们自己显式持有(retain)和释放(release)对象,ARC通过对对像加上所有权修饰符(__strong等),编译器通过对象的所有权修饰符将会自动键入引用计数管理(根据所有权修饰符自动键入retain、release、autorelease)
本文主要叙述引用计数的实现原理,ARC和MRC在使用上的区别,以及编译器在ARC中为我们做了什么。
Objective-C对象的MRC
- retain
- release
- autorelease
autorelease的实现
使用栈(后进先出)来管理NSAutoreleasePool对象,因此可以随时拿到最近(hotPage)的NSAutoreleasePool,调用对象的autorelease方法,会在hotPage中的内部数组将对象加入进去。当NSAutoreleasePool出栈时,调用内部数组中元素的release方法即可。
retain/release的实现
引用计数表:使用散列表实现引用计数表,key为对象的地址的散列值,value为引用计数和内存块地址。
retain:通过对象的地址在引用计数表中找到引用计数,如果retainCount超过最大值,则抛异常,否则retainCount加1。
release:通过对象的地址在引用计数表中找到引用计数,如果retainCount值为0,则抛异常。否则retainCount减1,如果retainCount减1后为0,则从引用计数表冲移除,并调用对象的dealloc方法。
Objective-C对象的ARC
Objective-C对象的ARC是通过所有权修饰符来管理对象的持有和释放。所有权修饰符一共有4种:
- __strong 修饰符,默认的修饰符
- __weak 修饰符
- __unsafe_unretained 修饰符
- __autoreleasing 修饰符
__strong修饰符的实现
取得自己生成并且持有对象:
使用ARC:
{
id obj1 = [NSObject new];
//相当于
//id __strong obj1 = [NSObject new];
}
不使用ARC:
{
id obj1 = [NSObject new];
//在变量作用域结束时插入release
[obj1 release];
}
取得非自己生成并持有对象:
使用ARC:
- (void)create {
test = [self object];
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}
不使用ARC:
- (void)create {
if (test) {
NSLog(@"MRC------------------MRC");
NSLog(@"release");
[test release];
}
test = [self object];
[test retain];
NSLog(@"MRC------------------MRC");
NSLog(@"after create count = %ld", [test retainCount]);
}
- (id)object {
id obj = [[MyObject alloc] init];
[obj autorelease];
return obj;
}
- (void)printRetainCount {
NSLog(@"MRC------------------MRC");
NSLog(@"retain count = %ld", [test retainCount]);
}
打印结果:
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:51.212 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.213 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] release**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:36:03.540 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:36:03.543 ARCLearn[53676:16388070] retain count = 1**
可以看到使用ARC的对象持有,在返回对象时,并没有把对象注册到autoreleasepool中,下面为ARC的autoreleasepool打印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 13 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: ##############**
下面为MRC的autoreleasepool打印:
**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 14 releases pending.**
**objc[54013]: [0x7fa3c9811000] ................ PAGE (hot) (cold)**
**objc[54013]: [0x7fa3c9811038] ################ POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040] 0x60800002e3c0 __NSCFString**
**objc[54013]: [0x7fa3c9811048] ################ POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811058] 0x7fa3ca800850 UIScreen**
**objc[54013]: [0x7fa3c9811060] 0x6000002712c0 __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068] 0x7fa3c8600bf0 UIWindow**
**objc[54013]: [0x7fa3c9811070] 0x60000008e6f0 __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078] 0x6000000567d0 __NSSetM**
**objc[54013]: [0x7fa3c9811080] 0x600000052de0 __NSSetM**
**objc[54013]: [0x7fa3c9811088] 0x600000053b30 __NSSetM**
**objc[54013]: [0x7fa3c9811090] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c9811098] 0x600000271640 __NSCFString**
**objc[54013]: [0x7fa3c98110a0] 0x608000017200 MyObject**
**objc[54013]: ##############**
在autoreleasepool里有MyObject对象。
为什么ARC没有把对象放到autoreleasepool里了?它是怎样持有非自己生成的对象的?
代码:
{
id __strong obj = [NSMutableArray array];
}
转换为编译器模拟代码:
{
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
}
objc_retainAutoreleasedReturnValue函数,顾名思义:让obj持有(retain)池中(autoreleased)返回的值(retuenValue);
代码:
+ (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注册在池中(调用obj的autorelease方法),并返回。
因此按照上面的理解objc_autoreleaseReturnValue
将返回对象注册到池子中,objc_retainAutoreleasedReturnValue`持有池中的对象。
但是事实不是那么简单:
但是objc_autoreleaseReturnValue远远不是autorelease那么简单。objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用了objc_retainAutoreleasedReturnValue,那就不会将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。
因此objc_retainAutoreleasedReturnValue也不是retain那么简单,即使放回对象不注册到autoreleasepool中,也能正确的获取对象。
通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。
__weak修饰符的实现
现在我们添加一个weakTest变量,使用weak所有权修饰符
在ARC中实现:
@interface ARC() {
id test;
id __weak weakTest;
}
@end
@implementation ARC
- (void)create {
test = [self object];
weakTest = test;
_objc_autoreleasePoolPrint();
NSLog(@"ARC------------------ARC");
NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}
- (id)object {
id obj = [[MyObject alloc] init];
return obj;
}
- (void)printRetainCount {
NSLog(@"ARC------------------ARC");
NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}
@end
打印结果:
**2016-12-13 16:24:30.934 ARCLearn[55038:16489628] ARC------------------ARC**
**2016-12-13 16:24:30.935 ARCLearn[55038:16489628] after create count = 1**
由此可以看出__weak并没有增加引用个数。
添加empty函数,使test为nil:
- (void)empty {
if (weakTest) {
NSLog(@"weak is %p", weakTest);
}
test = nil;
if (!weakTest) {
NSLog(@"weak change to nil");
}
}
打印结果:
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] weak is 0x608000011530**
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] myobject dealloc**
**2016-12-13 16:33:06.302 ARCLearn[55349:16509066] weak change to nil**
由此可以看出若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量。
是如何实现将nil值赋值给引用为0的__weak对象?
{
id __weak obj1 = obj;
}
转换为编译器模拟代码:
{
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(obj1);
}
其中objc_initWeak(&obj1,obj)的实现:
obj1 = 0;
objc_storyWeak(&obj1, obj);
objc_destroyWeak的实现:
objc_storyWeak(&obj1, 0);
所以合在一起是:
{
id obj1;
obj1 = 0;
objc_storyWeak(&obj1, obj);
objc_storyWeak(&obj1, 0);
}
其中objc_storyWeak就是针对weak表(散列表)操作:
- 注册到表中:objc_storeWeak函数把第二个参数的地址(散列值)作为键,将第一个参数的地址加入链表中(键值为一个链表,由于一个对象可同时赋值给多个附有__weak修饰符的变量),完成weak表的注册。
- 从表中移除:如果第二个参数为0,则把第二个参数的地址的键从weak表中删除,并且将键值置为nil(将链表中的值都置为nil)。
- 从表中查询:使用weak表,将废弃对象的地址作为键进行检索,就能告诉的获取对应的附有__weak修饰符的变量的地址。
废弃引用为0的对象的流程:
- objc_release
- 因为引用计数为0所以执行dealloc
- 从weak表中获取废弃对象的地址为键的记录
- 将包含在记录中所有附有__weak修饰符变量的地址,赋值为nil
- 从weak表中删除该记录
- 从引用计数表中删除废弃对象的地址为键的记录
__autoreleasing修饰符的实现
将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法。
ARC有效:
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];
id __autoreleasing obj2 = obj1;
id __strong obj3 = [NSMutableArray array];
id __autoreleasing obj4 = obj3;
}
ARC无效:
@autoreleasepool {
id obj = [[NSObject alloc] init];
[obj autorelease];
id obj1 = [[NSObject alloc] init];
id obj2 = obj1;
[obj2 retain];
[obj2 autorelease];
id obj3 = [NSMutableArray array];
[obj3 retain];
id obj4 = obj3;
[obj4 retain];
[obj4 autorelease];
[obj3 release];
}
注意:显式的附加autoreleasing修饰符同显式地添加strong修饰符一样罕见。我们经常非显式的使用__autoreleasing修饰符。
隐式使用__autoreleasing:
- 编译器检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool(objc_retainAutoreleasedReturnValue和objc_autoreleaseReturnValue成对出现时就不会注册到autoreleasepool)。
- id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符。
如:
NSError *error = nil;
BOOL result = [obj performOperationWithError: &error];
performOperationWithError声明为:
- (BOOL)performOperationWithError:(NSError **)error;
自动添加__autoreleasing的代码:
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
/* 错误发生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
非ARC的实现:
- (BOOL)performOperationWithError:(NSError **)error {
/* 错误发生 */
*error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
[*error autorelease];
return NO;
}
因为声明为NSError __autoreleasing 类型的error作为*error被赋值,所以能够返回注册到autoreleasepool中的对象。
注意:赋值给对象指针时,所有权修饰符必须一致。
编译器会自动加上所有权修饰符:NSError * __strong error = nil; NSError * __autoreleasing *pError = &error;
所有权修饰符不一致会产生编译错误:
Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
__unsafe_unretained 修饰符
unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
ARC实现:
id obj = [[NSObject alloc] init];
id __unsafe_unretained obj1 = obj;
非ARC实现:
id obj = [[NSObject alloc] init];
id obj1 = obj;
所以就是...编译器啥都不做。这样当对象的引用计数为0,被废弃后,obj1就是不安全的了(悬垂指针),因为不会被置为nil。
注意:使用时确保对象确实存在。
什么时候使用__unsafe_unretained?
- 在iOS4的应用程序中,必须使用unsafe_unretained修饰的变量,来替代weak修饰符。
- 不支持__weak修饰符的类,例如NSMachPork类,因为这些类重写了retain/release并实现该类独自的引用计数机制。
- allowsWeakReference/retainWeakReference实例方法(没有写入NSObject接口说明文档中)返回NO的情况。
Core Foundation对象的ARC
Core Foundation对象主要使用在用C语言编写的Core Foundation框架中,并使用引用计数的对象。
- 在ARC无效时,Core Foundation框架中的retain/release分别是CFRetain/CFRelease。
- Foundation框架的API生成并持有的对象可以用Core Foundation框架的API释放。当然,反过来也是可以的。
- 因为Core Foundation对象与Objective-C对象没有区别,所以ARC无效时,只用简单的C语言的转换也能实现互换。因为这种转换不需要使用额外的CPU资源,因此也被称为“免费桥”(Toll-free Bridge)。
ARC无效时:
id obj = [[NSObject alloc] init];
void *p = obj; //void *相当与oc的id
id o = p;
[o release];
ARC有效时,会引起编译错误,要使用“__bridge”转换。
ARC有效时的代码:
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;
id o = (__bridge id)p;
[o release];
注意:bridge的安全性与赋值给unsafe_unretained修饰符相近,甚至会更低,如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。
__bridge转换中还有另两中转换,分别为:
- bridge_retained转换,相当于赋值给strong变量。
ARC有效:
ARC无效:id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void*)obj;
id obj = [[NSObject alloc] init]; void *p = obj; [(id)p retain];
- bridge_transfer转换,相当于赋值给strong变量后,该变量随之释放。
ARC有效
ARC无效void *p = 0; id obj = (__bridge_transfer id)p;
void *p = 0; id obj = (id)p; [obj retain]; [(id)p release];
简书个人主页:www.jianshu.com/users/b92ab…