Objective-C中的自动释放池
自动释放池块@autoreleasepool
自动释放池块在MRC和ARC下都可以使用。在MARC下,为了将自动释放池块内部的变量放入自动释放池,需要手动调用autorelease方法;在ARC下,只能通过声明变量为__autoreleasing来达到目的,而不是自动释放池块内部的所有变量,都会进入自动释放池。
加入在ARC下,有如下代码:
@autoreleasepool { X *x = [[X alloc] init]; }
如果在Xcode控制台使用_objc_autoreleasePoolPrint()方法查看,会发现自动释放池没有任何变量。
但是如果我们进行如下调用:
@autoreleasepool { X *x = [X create]; //create返回X类型对象 }
然后使用_objc_autoreleasePoolPrint()方法查看,会发现自动释放池里面持有X对象:
(lldb) po _objc_autoreleasePoolPrint() objc[4574]: ############## objc[4574]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[4574]: 2 releases pending. objc[4574]: [0x10100c000] ................ PAGE (hot) (cold) objc[4574]: [0x10100c038] ################ POOL 0x10100c038 objc[4574]: [0x10100c040] 0x100509270 X objc[4574]: ############## 0x70a40db675eb00e6
这个持有的X对象是因为调用的create方法不是以alloc/new/init/copy/mutableCopy开头命名,因此编译器在create方法返回前,会自动将返回的变量放入到自动释放池,而不是因为在自动释放池块内部声明了变量x导致的。
如果我们将变量x加上__autoreleasing属性,就看的很明白了,对象X在自动释放池中放入了两次:
(lldb) po _objc_autoreleasePoolPrint() objc[4667]: ############## objc[4667]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[4667]: 3 releases pending. objc[4667]: [0x103803000] ................ PAGE (hot) (cold) objc[4667]: [0x103803038] ################ POOL 0x103803038 objc[4667]: [0x103803040] 0x102928700 X objc[4667]: [0x103803048] 0x102928700 X objc[4667]: ############## 0xb8c9f2d34f6300b0
将变量放入到自动释放池中,不会增加变量的引用计数
要看清这个问题,可以在MRC下将变量多次调用autorelease方法,然后使用CFGetRetainCount方法在Xcode控制台进行查看:
//MRC @autoreleasepool { X *x = [[X alloc] init]; [x autorelease]; [x autorelease]; [x autorelease]; }
控制台查看的结果如下,引用技术为1:
(lldb) p CFGetRetainCount((__bridge CFTypeRef)x) (CFIndex) $0 = 1 (lldb)
在ARC下,结论依然不变,但是更具迷惑性。
//ARC @autoreleasepool { X __autoreleasing *x = [[X alloc] init]; }
使用Xcode控制台查看,会发现变量X被放入了自动释放池,并且引用计数为1,符合预期:
(lldb) po _objc_autoreleasePoolPrint() objc[4961]: ############## objc[4961]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[4961]: 2 releases pending. objc[4961]: [0x104003000] ................ PAGE (hot) (cold) objc[4961]: [0x104003038] ################ POOL 0x104003038 objc[4961]: [0x104003040] 0x10301c820 X objc[4961]: ############## 0x3d01f52ffddf001a (lldb) p CFGetRetainCount((__bridge CFTypeRef)x) (CFIndex) $1 = 1 (lldb)
再看下面的代码:
//ARC @autoreleasepool { X __autoreleasing *x = [X create];//create返回X类型对象 }
在Xcode控制台查看,会发现x变量被放入了两次自动释放池,这是符合预期的,但是x变量的引用计数却变成了2,给人的感觉是没放入一次自动释放池,计数就加1:
(lldb) po _objc_autoreleasePoolPrint() objc[5052]: ############## objc[5052]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[5052]: 3 releases pending. objc[5052]: [0x106003000] ................ PAGE (hot) (cold) objc[5052]: [0x106003038] ################ POOL 0x106003038 objc[5052]: [0x106003040] 0x1005176a0 X objc[5052]: [0x106003048] 0x1005176a0 X objc[5052]: ############## 0x4c2912a2a0dd00bc (lldb) p CFGetRetainCount((__bridge CFTypeRef)x) (CFIndex) $1 = 2 (lldb)
但是如果我们使用clang编译器的-S选项,将源码转换成汇编码,就会发现其中的奥秘:
.loc 1 41 32 ## autorelease.m:41:32 movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rsi movq L_OBJC_SELECTOR_REFERENCES_.11(%rip), %rcx movq %rsi, %rdi movq %rcx, %rsi movq %rax, -32(%rbp) ## 8-byte Spill callq *_objc_msgSend@GOTPCREL(%rip) .loc 1 41 28 is_stmt 0 ## autorelease.m:41:28 41行源码就是X __autoreleasing *x = [X create]; movq %rax, %rdi callq _objc_retainAutoreleasedReturnValue ##这个Retain方法将返回的值被持有了一次,所以引用计数变成了2 movq %rax, %rdi callq _objc_autorelease leaq L__unnamed_cfstring_.13(%rip), %rcx movq %rax, -24(%rbp)
在看下面这种情况,和上面的例子的效果一样:
//ARC @autoreleasepool { X __autoreleasing *x1 = [X create]; X __autoreleasing *x2 = x1; }
在Xcode中查看输出,自动释放池里面有3个变量,符合预期,引用计数为3,符合预期:
(lldb) po _objc_autoreleasePoolPrint() objc[5401]: ############## objc[5401]: AUTORELEASE POOLS for thread 0x1000aa5c0 objc[5401]: 4 releases pending. objc[5401]: [0x102004000] ................ PAGE (hot) (cold) objc[5401]: [0x102004038] ################ POOL 0x102004038 objc[5401]: [0x102004040] 0x100708950 X objc[5401]: [0x102004048] 0x100708950 X objc[5401]: [0x102004050] 0x100708950 X objc[5401]: ############## 0xfbeaa82f2b80004a (lldb) p CFGetRetainCount((__bridge CFTypeRef)x1) (CFIndex) $1 = 3 (lldb)
查看汇编码可知:
.loc 1 41 28 is_stmt 0 ## autorelease.m:41:28 调用[X create]源码处 movq %rax, %rdi callq _objc_retainAutoreleasedReturnValue ##第一处Retain方法 movq %rax, %rdi callq _objc_autorelease movq %rax, -24(%rbp) .loc 1 42 28 is_stmt 1 ## autorelease.m:42:28 复制x2 = x1处 movq -24(%rbp), %rax movq %rax, %rdi callq _objc_retainAutorelease #第二处retain方法,这个方法retain的同时,将变量放入自动释放池 leaq L__unnamed_cfstring_.13(%rip), %rcx movq %rax, -32(%rbp)