本文为 《Objective-C 高级编程》1.4 中的内容编程
ARC 由如下工具、库来实现:bash
经过 clang 汇编输出和 objc4 库(主要是 runtime/objc-arr.mm)的源代码进行说明。架构
赋值给附有 __strong 修饰符的变量在实际的程序中究竟是怎样运行的呢?app
{
id __strong obj = [[NSObject alloc] init];
}
复制代码
在编译器选项 “-S” 的同时运行 clang,可取得程序汇编输出。看看汇编输出和 objc4 库的源代码就可以知道程序是如何工做的。该源代码实际上可转换为调用如下的函数。为了便于理解,之后的源代码有时也使用模拟源代码。框架
/* 编译器的模拟代码*/
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
复制代码
如原源代码所示,2次调用 objc_msgSend 方法(alloc 方法和 init 方法),变量做用域结束时经过 objc_release 释放对象。虽然 ARC 有效时不能使用 release 方法,但由此可知编译器自动插入了 release。下面咱们来看看使用 alloc/new/copy/mutableCopy 之外的方法会是什么状况。ide
{
id __strong obj = [NSMutableArray array];
}
复制代码
虽然调用了咱们熟知的 NSMutableArray 类的 array 类方法,但获得的结果却与以前稍有不一样。函数
/* 编译器的模拟代码*/
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
复制代码
虽然最开始的 array 方法的调用以及最后变量做用域结束时的 release 与以前相同,但中间的 objc_retainAutoreleasedReturnValue 函数是什么呢?工具
objc_retainAutoreleasedReturnValue 函数主要用于最优化程序运行。顾名思义,它是用于本身持有(retain)对象的函数,但它持有的对象应为返回注册在 autoreleasepool 中对象的方法,或是函数的返回值。像该源代码这样,在调用 alloc/new/copy/mutableCopy 之外的方法,即 NSMutableArray 类的 array 类方法等调用以后,由编译器插入该函数。优化
这种 objc_retainAutoreleasedReturnValue 函数是成对的,与之相对的函数是 objc_autoreleaseReturnValue。它用于 alloc/new/copy/mutableCopy 方法之外的 NSMutableArray 类的 array 类方法等返回对象的实现上。下面咱们看看 NSMutableArray 类的 array 类经过编译器会进行怎样的转换。ui
+(id)array {
return [[NSMutableArray alloc] init];
}
复制代码
如下为该源代码的转换,转换后的源代码使用了 objc_autoreleaseReturnValue 函数。
+(id)array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
复制代码
像该源代码这样,返回注册到 autoreleasepool 中对象的方法使用了 objc_autoreleaseReturnValue 函数返回注册到 autoreleasepool 中的对象。可是 objc_autoreleaseReturnValue 函数同 objc_autorelease 函数不一样,通常不只限于注册对象到 autoreleasepool 中。
objc_autoreleaseReturnValue 函数会检查使用该函数的方法或函数调用方的执行列表,若是方法或函数的调用方在调用了方法或函数后紧接着调用 objc_retainAutoreleaseReturnValue() 函数,那么就不将返回的对象注册到 autorelease 中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue 函数与 objc_retain 函数不一样,它即使不注册到 autoreleasepool 中而返回对象,也可以正确地获取对象。经过 objc_autoreleaseReturnValue 函数和 objc_retainAutoreleasedReturnValue 函数的写做,能够不将对象注册到 autorelease 中而直接传递,这一过程达到了最优化。
objc_autoreleaseReturnValue 实现以下:
// Prepare a value at +1 for return through a +0 autoreleasing convention.
id objc_autoreleaseReturnValue(id obj) {
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
return objc_autorelease(obj);
}
复制代码
prepareOptimizedReturn 是优化返回的关键,其实现以下:
// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
assert(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
复制代码
gcc 的编译特性使用 __builtin_return_address(level) 打印出一个函数的堆栈地址。其中 level 表明是堆栈中第几层调用地址,__builtin_return_address(0) 表示第一层调用地址,即当前函数,__builtin_return_address(1) 表示第二层。
callerAcceptsOptimizedReturn 在 arm 架构上的实现以下:
static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) {
// if the low bit is set, we're returning to thumb mode
if ((uintptr_t)ra & 1) {
// 3f 46 mov r7, r7
// we mask off the low bit via subtraction
// 16-bit instructions are well-aligned
if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
return true;
}
} else {
// 07 70 a0 e1 mov r7, r7
// 32-bit instructions may be only 16-bit aligned
if (*(unaligned_uint32_t *)ra == 0xe1a07007) {
return true;
}
}
return false;
}
复制代码
关于这段代码的含义请看这篇文章,总结的比我好:How does objc_retainAutoreleasedReturnValue work?
objc_retainAutoreleasedReturnValue 实现以下,基本就是 objc_autoreleaseReturnValue 的逆操做。
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id objc_retainAutoreleasedReturnValue(id obj) {
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
复制代码
就像前面咱们看到的同样,__weak 修饰符提供的功能如同魔法通常。
这些功能像魔法同样,到底发生了什么,咱们一无所知。因此下面咱们来看看它们的实现。
{
id __weak obj1 = obj;
}
复制代码
假设变量 obj 附加 __strong 修饰符且对象被赋值。
/* 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj1, obj);
objc_destoryWeak(&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 表,将废弃对象的地址做为键值进行检索,就能高速地获取对应的附有 __weak 修饰符的变量的地址。另外,因为一个对象可同时赋值给多个附有 __weak 修饰符的变量中,因此对于一个键值,可注册多个变量的地址。
释放对象时,废弃谁都不持有的对象的同时,程序的动做是怎样的呢?下面咱们来跟踪观察。对象将经过 objc_release 函数释放。
对象被废弃时最后调用的 objc_clear_deallocating 函数的动做以下:
根据以上步骤,前面说的若是附有 __weak 修饰符的变量所引用的对象被废弃,则将 nil 赋值给该变量这一功能即被实现。由此能够,若是大量使用附有 __weak 修饰符的变量,则会消耗相应的 CPU 资源,良策是只在须要避免循环引用时使用 __weak 修饰符。
使用 __weak 修饰符时,如下源代码会引发编译器警告。
id __weak obj = [[NSObject alloc] init];
复制代码
由于该源代码将本身生成并持有的对象赋值给附有 __weak 修饰符的变量中,因此本身不能持有该对象,这时会被释放并废弃,所以会引发编译器警告。
编译器会如何处理改源代码呢?
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init))l
objc_initWeak(tmp);
objc_release(tmp);
objc_destroyWeak(&object);
复制代码
虽然本身生成并持有对象经过 objc_initWeak 函数被赋值给附有 __weak 修饰符的变量中,但编译器判断其没有持有者,故该对象当即经过 objc_release 函数被释放和废弃。
这样一来,nil 就会被赋值给引用废弃对象的附有 __weak 修饰符的变量中。下面咱们经过 NSLog 函数来验证一下。
id __weak obj = [[NSObject alloc] init];
NSLog(@"obj=%@", obj);
复制代码
如下为该源代码的输出结果,其中用 %@ 输出 nil。
obj=(null)
复制代码
此次咱们再用附有 __weak 修饰符的变量来确认另外一功能:使用附有 __weak 修饰符的变量,便是使用注册到 autoreleasepool 中的对象。
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
复制代码
该源代码可转换为以下形式:
/* 编译器的模拟代码 */
id objl
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
复制代码
与被赋值时相比,在使用附有 __weak 修饰符变量的情形下,增长了对 objc_loadWeakRetained 函数和 objc_autorelease 函数的调用。这些函数的动做以下:
由此可知,由于附有 __weak 修饰符变量缩阴用的对象像这样被注册到 autoreleasepool 中,因此在 @autoreleasepool 块结束以前均可以放心使用。可是,若是大量地使用附有 __weak 修饰符的变量,注册到 autorelease 的对象也会大量地增长,所以在使用附有 __weak 修饰符的变量时,最好先暂时赋值给附有 __weak 修饰符的变量后再使用。
{
id __weak o = obj;
NSLog(@"1 %@", o);
NSLog(@"2 %@", o);
NSLog(@"3 %@", o);
NSLog(@"4 %@", o);
NSLog(@"5 %@", o);
}
复制代码
相应地,变量 o 所赋值的对象也就注册到 autoreleasepool 中 5 次。
将附有 __weak 修饰符的变量 o 赋值给附有 __strong 修饰符的变量后再使用能够避免此类问题。
{
id __weak o = obj;
id tmp = o;
NSLog(@"1 %@", tmp);
NSLog(@"2 %@", tmp);
NSLog(@"3 %@", tmp);
NSLog(@"4 %@", tmp);
NSLog(@"5 %@", tmp);
}
复制代码
在 "tmp = 0;" 时对象仅登陆到 autoreleasepool 中1次。
在 iOS 4 和 OS X Snow Leopard 中是不能使用 __weak 修饰符的,而有时在其余环境下也不能使用。实际上存在着不支持 __weak 修饰符的类。
例如 NSMachPort 类就是不支持 __weak 修饰符的类。这些类重写了 retain/release 并实现该类肚子的引用计数机制。可是赋值以及使用附有 __weak 修饰符的变量都必须恰当地使用 objc4 运行时库中的函数,所以独自实现引用计数机制的类大多不支持 __weak 修饰符。
不支持 __weak 修饰符的类,在其类声明中附加了 "__attribute ((objc_arc_weak_reference_unavailable))" 这一属性,同时定义了 NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE。若是将不支持 __weak 声明类的对象赋值给附有 __weak 修饰符的变量,那么一旦编译器检验出来就会报告编译错误。并且在 Cocoa 框架类中,不支持 __weak 修饰符的类极为罕见,所以没有必要太过担忧。
objc_initWeak 方法的实现以下:
/** * Initialize a fresh weak pointer to some object location. * It would be used for code like: * * (The nil case) * __weak id weakPtr; * (The non-nil case) * NSObject *o = ...; * __weak id weakPtr = o; * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location Address of __weak ptr. * @param newObj Object ptr. */
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
复制代码
objc_destroyWeak 实现以下:
/** * Destroys the relationship between a weak pointer * and the object it is referencing in the internal weak * table. If the weak pointer is not referencing anything, * there is no need to edit the weak table. * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location The weak pointer address. */
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
复制代码
/** * This function stores a new value into a __weak variable. It would * be used anywhere a __weak variable is the target of an assignment. * * @param location The address of the weak pointer itself * @param newObj The new object this weak ptr should now point to * * @return \e newObj */
id
objc_storeWeak(id *location, id newObj)
{
return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object *)newObj);
}
复制代码
看了上述代码咱们能够知道,objc_initWeak、objc_destroyWeak 和 objc_storeWeak 三个方法本质上都调用了 storeWeak 方法,其实现以下
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj) {
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
复制代码
如前所述,如下源代码会引发编译警告。
id __weak obj = [[NSObject alloc] init];
复制代码
这是因为编译器判断生成并持有的对象不能继续持有。附有 __unsafe_unretained 修饰符的变量又如何呢?
id __unsafe_unretained obj = [[NSObject alloc] init];
复制代码
与 __weak 修饰符彻底相同,编译器判断生成并持有的对象不能继续持有,从而发出警告。
该源代码经过编译器转换为如下形式。
/* 编译器的模拟代码 */
id obj = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
复制代码
objc_release 函数当即释放了生成并持有的对象,这样该对象的悬垂指针被赋值给变量 obj 中。
那么若是最初不赋值变量又会如何呢?下面的源代码在 ARC 无效时一定会发生内存泄漏。
[[NSObject alloc] init];
复制代码
因为源代码不使用返回值的对象,因此编译器发出警告。
可像下面这样经过向 void 型转换来避免发生警告。
(void)[[NSObject alloc] init];
复制代码
不论是否转换为 void,该源代码都会转换为如下形式。
/* 编译器的模拟代码 */
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
复制代码
虽然没有指定赋值变量,但与赋值给附有 __unsafe_unretained 修饰符变量的源代码彻底相同。因为不能继续持有生成并持有的对象,因此编译器生成了当即调用 objc_release 函数的源代码。而因为 ARC 的处理,这样的源代码也不会形成内存泄露。
另外,能调用被当即释放的对象的示例方法吗?
(void)[[[NSObject alloc] init] hash];
复制代码
该源代码可变为以下形式:
/* 编译器的模拟代码 */
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
复制代码
在调用了生成并持有对象的实例方法后,该对象被释放。看来“由编译器进行内存管理”这句话应该是正确的。
实际上还有一种状况也不能使用 __weak 修饰符。
就是当 allowsWeakReference/retainWeakReference 实例方法(没有写入 NSObject 接口说明文档中)返回 NO 的状况。这些方法的声明以下:
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
复制代码
在赋值给 __weak 修饰符的变量时,若是赋值对象的 allowsWeakReference 方法返回 NO,程序将异常终止。
即对于全部 allowsWeakReference 方法返回 NO 的类都绝对不能使用 __weak 修饰符。这样的类一定在其参考说明中有所记述。
另外,在使用 __weak 修饰符的变量时,当被赋值对象的 retainWeakReference 方法返回 NO 的状况下,该变量将使用“nil”。如如下的源代码:
{
id __strong obj = [[NSObject alloc] init];
id __weak o = obj;
NSLog(@“1 %@”, o);
NSLog(@“2 %@”, o);
NSLog(@“3 %@”, o);
NSLog(@“4 %@”, o);
NSLog(@“5 %@”, o);
}
复制代码
因为最开始生成并持有的对象为附有 __strong 修饰符变量 obj 所持有的强引用,因此在该变量做用域结束以前都始终存在。所以以下所示,在变量做用域结束以前,没能够持续使用附有 __weak 修饰符的变量 o 所引用的对象。
下面对 retainWeakReference 方法进行实验。咱们作一个 MyObject 类,让其继承 NSObject 类并实现 retainWeakReference 方法。
@interface MyObject : NSObject
{
NSUInteger count;
}
@end
@implementation MyObject
- (id)init
{
self = [super init];
return self;
}
- (BOOL)retainWeakReference
{
if (++count > 3)
return NO;
return [super retainWeakReference];
}
@end
复制代码
该例中,当 retainWeakReference 方法被调用4次或4次以上时返回 NO。在以前的源代码中,将从 NSObject 类生成并持有对象的部分更改成 MyObject 类。
{
id __strong obj = [[MyObject alloc] init];
id __weak o = obj;
NSLog(@"1 %@", o);
NSLog(@"2 %@", o);
NSLog(@"3 %@", o);
NSLog(@"4 %@", o);
NSLog(@"5 %@", o);
}
复制代码
如下为执行结果。
从第4次起,使用附有 __weak 修饰符的变量 o 时,因为所饮用对象的 retainWeakReference 方法返回 NO,因此没法获取对象。像这样的类也一定在其参考说明中有所记述。
另外,运行时库为了操做 __weak 修饰符在执行过程当中调用 allowsWeakReference/retainWeakReference 方法,所以从该方法中再次操做运行时库时,其操做内容会永久等待。本来这些方法并无计入文档,所以应用程序编程人员不可能实现该方法群,但若是因某些缘由而不得不实现,那么仍是在所有理解的基础上实现比较好。
将对象赋值给附有 __autoreleasing 修饰符的变量等同于 ARC 无效时调用对象的 autorelease 方法。咱们经过如下源代码来看一下。
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
复制代码
该源代码主要将 NSObject 类对象注册到 autoreleasepool 中,可做以下变换:
/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
复制代码
这与苹果的 autorelease 实现中的说明彻底相同。虽然 ARC 有效和无效时,其在源代码上的表现有所不一样,但 autorelease 的功能彻底同样。
在 alloc/new/copy/mutableCopy 方法群以外的方法中使用注册到 autoreleasepool 中的对象会如何呢?下面咱们来看看 NSMutableArray 类的 array 类方法。
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}
复制代码
这与前面的源代码有何不一样呢?
/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
复制代码
虽然持有对象的方法从 alloc 方法变为 objc_retainAutoreleasedReturnValue 函数,但注册 autoreleasepool 的方法没有改变,还是 objc_autorelease 函数。