ARC ARC (Automatic Reference Counting) 是由编译器跟运行时共同完成的(运行时标记);编译器会在编译时会自动加上 retain、release、autorelease、dealloc 操作。
__autoreleasing 如果一个变量被用关键字修饰 __autoreleasing 修饰,那么变量会立即加入到自动释放池中
ARC规则
若方法名以下列词语开头,则其返回的对象归调用者所有:alloc
new
copy
mutableCopy
。归调用者所有的意思是:调用上述四种方法的那段代码要负责释放方法所返回的对象。
除了会自动调用“保留”与“释放”方法外,ARC 还可以执行一些手工操作很难甚至无法完成的优化。如果发现在同一个对象上执行多次“保留”与“释放”操作,那么ARC有时可以成对地移除这两个操作。 一般,在方法中返回自动释放的对象时,要执行一个特殊函数。此时不直接调用对象的 autorelease 方法,而是改为调用 objc_autoreleaseReturnValue 。此函数会检视当前方法返回之后即将要执行的那段代码。若发现那段代码在返回的对象上执行 retain 操作,则设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位而不执行 autorelease 操作。与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行 retain,而是改为执行 objc_retainAutoreleaseReturnValue 函数。此函数要检测刚才提到的那个标志位,若已经置位,则不执行 retain 操作。设置并检测标志位,要比调用 autorelease 和 retain 更快。备注 :objc_autoreleaseReturnValue 优化不一定开启,会根据不同CPU类型决定另外,这个标记位存在哪里呢?关键字:线程局部存储(TLS)
objc_autoreleaseReturnValue 相关代码逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 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); } static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { ASSERT(getReturnDisposition() == ReturnAtPlus0); //callerAcceptsOptimizedReturn 是个条件编译选项,不同CPU类型代码完全不一样 if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { if (disposition) setReturnDisposition(disposition); return true; } return false; }
例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 方法名以关键字 new 开头,ARC 不会加入 retain、release 或 autorelease 语句。 + (VCHPerson *)newPerson { VCHPerson *person = [[VCHPerson alloc] init]; return person; } // 方法名不以关键字开头,ARC 会自动加上 autorelease 语句。 + (VCHPerson *)somePerson { VCHPerson *person = [[VCHPerson alloc] init]; return person; } // ARC 会在函数末尾给 personOne 加上 release 语句。 // 而 somePerson 已经在方法内部加入到释放池中了 - (void)doSomething { VCHPerson *personOne = [VCHPerson newPerson]; VCHPerson *personTwo = [VCHPerson somePerson]; }
内部的实现逻辑可以用以下代码代替
1 2 3 4 5 6 7 8 9 //非关键字开头 + (instancetype)object { return [[NSObject alloc] init]; // 实际会变成 return objc_autoreleaseReturnValue([[NSObject alloc] init]); } NSObject *object = [NSObject object]; // 实际会变成 NSObject *object = objc_retainAutoreleasedReturnValue([NSObject object]);
再来看看汇编 一、外部方法非关键字开头,内部方法关键字开头
NSMutableArray 以 alloc 方式生成对象,引用计数为1,此时没有加入到自动释放池中
init方法不做任何处理,猜测是编译器行为
当前方法名不以关键字开头,return时需要加入到自动释放池中,表示当前方法内持有对象,并负责释放对象。
但是由于编译器优化,不会立即加入到自动释放池中,而是调用 objc_autoreleaseReturnValue 方法标记对象(从下面的汇编代码可以看出确实调用了 objc_autoreleaseReturnValue 方法)。 如果对象返回后,又有别的变量需要 retain 这个对象,则编译器会调用这个方法 objc_retainAutoreleasedReturnValue,此时会检查对象的是否已经被标记,如果已经被标记,则相互抵消,并将标记位清除,如果没有被标记则最终调用 retain 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 - (id)getObjectWithAlloc { id obj = [[NSMutableArray alloc] init]; return obj;//断点 /* VCHTest`-[AppDelegate getObjectWithAlloc]: 0x10027e210 <+0>: pushq %rbp 0x10027e211 <+1>: movq %rsp, %rbp 0x10027e214 <+4>: subq $0x20, %rsp 0x10027e218 <+8>: movq %rdi, -0x8(%rbp) 0x10027e21c <+12>: movq %rsi, -0x10(%rbp) 0x10027e220 <+16>: movq 0x9c41(%rip), %rdi ; (void *)0x00007fff801e6298: NSMutableArray 0x10027e227 <+23>: callq 0x10027f814 ; symbol stub for: objc_alloc_init 0x10027e22c <+28>: movq %rax, -0x18(%rbp) -> 0x10027e230 <+32>: movq -0x18(%rbp), %rdi 0x10027e234 <+36>: movq 0x3df5(%rip), %rax ; (void *)0x00007fff20191840: objc_retain 0x10027e23b <+43>: callq *%rax 0x10027e23d <+45>: xorl %ecx, %ecx 0x10027e23f <+47>: movl %ecx, %esi 0x10027e241 <+49>: leaq -0x18(%rbp), %rdi 0x10027e245 <+53>: movq %rax, -0x20(%rbp) 0x10027e249 <+57>: callq 0x10027f86e ; symbol stub for: objc_storeStrong 0x10027e24e <+62>: movq -0x20(%rbp), %rdi 0x10027e252 <+66>: addq $0x20, %rsp 0x10027e256 <+70>: popq %rbp 0x10027e257 <+71>: jmp 0x10027f826 ; symbol stub for: objc_autoreleaseReturnValue */ }
二、外部方法非关键字开头,内部方法非关键字开头
NSMutableArray 以 array 方式生成对象,引用计数为1,并加入到自动释放池中(标记)
调用 objc_retainAutoreleasedReturnValue,因为临时变量需要持有对象,此时标记位清除
由于方法名不以关键字开头,return 时调用 objc_autoreleaseReturnValue 加入到自动释放池中(同样也是先标记)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 - (id)getObjectWithArray { id obj = [NSMutableArray array]; return obj;//断点 /* VCHTest`-[AppDelegate getObjectWithArray]: 0x10027e260 <+0>: pushq %rbp 0x10027e261 <+1>: movq %rsp, %rbp 0x10027e264 <+4>: subq $0x20, %rsp 0x10027e268 <+8>: movq %rdi, -0x8(%rbp) 0x10027e26c <+12>: movq %rsi, -0x10(%rbp) 0x10027e270 <+16>: movq 0x9bf1(%rip), %rdi ; (void *)0x00007fff801e6298: NSMutableArray 0x10027e277 <+23>: movq 0x9aba(%rip), %rsi ; "array" 0x10027e27e <+30>: movq 0x3d9b(%rip), %rax ; (void *)0x00007fff20175280: objc_msgSend 0x10027e285 <+37>: callq *%rax 0x10027e287 <+39>: movq %rax, %rdi 0x10027e28a <+42>: callq 0x10027f85c ; symbol stub for: objc_retainAutoreleasedReturnValue 0x10027e28f <+47>: movq %rax, -0x18(%rbp) -> 0x10027e293 <+51>: movq -0x18(%rbp), %rdi 0x10027e297 <+55>: movq 0x3d92(%rip), %rax ; (void *)0x00007fff20191840: objc_retain 0x10027e29e <+62>: callq *%rax 0x10027e2a0 <+64>: xorl %ecx, %ecx 0x10027e2a2 <+66>: movl %ecx, %esi 0x10027e2a4 <+68>: leaq -0x18(%rbp), %rdi 0x10027e2a8 <+72>: movq %rax, -0x20(%rbp) 0x10027e2ac <+76>: callq 0x10027f86e ; symbol stub for: objc_storeStrong 0x10027e2b1 <+81>: movq -0x20(%rbp), %rdi 0x10027e2b5 <+85>: addq $0x20, %rsp 0x10027e2b9 <+89>: popq %rbp 0x10027e2ba <+90>: jmp 0x10027f826 ; symbol stub for: objc_autoreleaseReturnValue */ }
三、外部方法关键字开头,内部方法关键字开头
NSMutableArray 使用 alloc 生成对象,引用计数为1,内部没有加入 AutoreleasePool 中;
随后调用 init 方法,这里 init 方法返回对象时没有加入到自动释放池中,猜测是编译器行为;
由于当前方法以 copy 开头,return 时既不调用 release,也不调用 autorelease;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 - (id)copyObjectWithAlloc { id obj = [[NSMutableArray alloc] init]; return obj;//断点 /* VCHTest`-[AppDelegate copyObjectWithAlloc]: 0x10027e160 <+0>: pushq %rbp 0x10027e161 <+1>: movq %rsp, %rbp 0x10027e164 <+4>: subq $0x20, %rsp 0x10027e168 <+8>: movq %rdi, -0x8(%rbp) 0x10027e16c <+12>: movq %rsi, -0x10(%rbp) 0x10027e170 <+16>: movq 0x9cf1(%rip), %rax ; (void *)0x00007fff801e6298: NSMutableArray 0x10027e177 <+23>: movq %rax, %rdi 0x10027e17a <+26>: callq 0x10027f814 ; symbol stub for: objc_alloc_init 0x10027e17f <+31>: movq %rax, -0x18(%rbp) -> 0x10027e183 <+35>: movq -0x18(%rbp), %rdi 0x10027e187 <+39>: callq *0x3ea3(%rip) ; (void *)0x00007fff20191840: objc_retain 0x10027e18d <+45>: xorl %ecx, %ecx 0x10027e18f <+47>: movl %ecx, %esi 0x10027e191 <+49>: leaq -0x18(%rbp), %rdi 0x10027e195 <+53>: movq %rax, -0x20(%rbp) 0x10027e199 <+57>: callq 0x10027f86e ; symbol stub for: objc_storeStrong 0x10027e19e <+62>: movq -0x20(%rbp), %rax 0x10027e1a2 <+66>: addq $0x20, %rsp 0x10027e1a6 <+70>: popq %rbp 0x10027e1a7 <+71>: retq */ }
四、外部方法关键字开头,内部方法非关键字开头
NSMutableArray 使用 array 生成对象,引用计数为1,内部已经加入 AutoreleasePool 中(其实还没有加入,只是被标记了而已);
通过下面的汇编代码可以看出,方法内调用了 objc_retainAutoreleasedReturnValue (因为有个临时变量需要持有当前对象),此时发现该对象已经被标记,则相互抵消,清除标志位,不加入自动释放池中;
由于当前方法以 copy 开头,return 时既不调用 release,也不调用 autorelease;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 - (id)copyObjectWithArray { id obj = [NSMutableArray array]; return obj;//断点 /* VCHTest`-[AppDelegate copyObjectWithArray]: 0x10027e1b0 <+0>: pushq %rbp 0x10027e1b1 <+1>: movq %rsp, %rbp 0x10027e1b4 <+4>: subq $0x20, %rsp 0x10027e1b8 <+8>: movq %rdi, -0x8(%rbp) 0x10027e1bc <+12>: movq %rsi, -0x10(%rbp) 0x10027e1c0 <+16>: movq 0x9ca1(%rip), %rax ; (void *)0x00007fff801e6298: NSMutableArray 0x10027e1c7 <+23>: movq 0x9b6a(%rip), %rsi ; "array" 0x10027e1ce <+30>: movq %rax, %rdi 0x10027e1d1 <+33>: callq *0x3e49(%rip) ; (void *)0x00007fff20175280: objc_msgSend 0x10027e1d7 <+39>: movq %rax, %rdi 0x10027e1da <+42>: callq 0x10027f85c ; symbol stub for: objc_retainAutoreleasedReturnValue 0x10027e1df <+47>: movq %rax, -0x18(%rbp) -> 0x10027e1e3 <+51>: movq -0x18(%rbp), %rdi 0x10027e1e7 <+55>: callq *0x3e43(%rip) ; (void *)0x00007fff20191840: objc_retain 0x10027e1ed <+61>: xorl %ecx, %ecx 0x10027e1ef <+63>: movl %ecx, %esi 0x10027e1f1 <+65>: leaq -0x18(%rbp), %rdi 0x10027e1f5 <+69>: movq %rax, -0x20(%rbp) 0x10027e1f9 <+73>: callq 0x10027f86e ; symbol stub for: objc_storeStrong 0x10027e1fe <+78>: movq -0x20(%rbp), %rax 0x10027e202 <+82>: addq $0x20, %rsp 0x10027e206 <+86>: popq %rbp 0x10027e207 <+87>: retq */ }
测试 下面代码执行会怎样
1 2 3 4 5 6 7 8 9 10 11 id __unsafe_unretained obj0 = nil; id __unsafe_unretained obj1 = nil; { NSArray *array0 = [self getObjectWithAlloc]; obj0 = array0; // NSArray *array1 = [self copyObjectWithAlloc]; obj1 = array1; } NSLog(@"vhuichen obj0 = %@", obj0); NSLog(@"vhuichen obj1 = %@", obj1);