ARC下关于autorelease的一些疑问
前言
今天在看黑幕背后的Autorelease这篇博客的时候,发现了一些问题。主要问题在于:
1 | // ARC 情况下 |
这里的array对象的retainCount到底是1还是2?
按照查找的博客,以及之前看的《Objective-C高级编程 iOS与OS X多线程和内存管理》这本书的结论:这里的retainCount应该是1,因为这个对象没有注册到autoreleasepool中。但是实际运行下来却并非如此。
Autorelease
AutoreleasePool 自动释放池
Autorelease就是延迟release的调用时间,将需要延迟release的对象加入autoreleasepool。当pool倾倒时,就开始调用pool中的对象的release方法。每一个线程对应一个自动释放池。
在ARC中,使用@autoreleasepool{}
来表示一个自动释放池。将OC代码通过clang的-rewrite-objc
,可以生成C++代码。可以看到自动释放池是一个__AtAutoreleasePool
C++对象。然后这个对象只是对AutoreleasePoolPage
对象的封装,可以在NSObject.mm
文件中找到它的详细定义。
下面这两篇博客对自动释放池有详细的解释:
Autorelease返回值的快速释放机制
1 | // ARC下 |
因为array
类方法是不持有对象的,那么正常情况下调用该方法时,该方法会将返回对象先注册到自动释放池;但是由于ARC下面存在这么一个机制,可以不将对象注册到自动释放池,直接给array持有。
这个机制主要由这两个函数构成:
objc_autoreleaseReturnValue(id);
objc_retainAutoreleasedReturnValue(id);
不过通过我的测试,发现这个说法有点说不通啊。来看看实际的情况
实地测试
测试的时候需要两个关键的函数:
uintptr_t _objc_rootRetainCount(id obj);
: 返回对象的retainCountvoid _objc_autoreleasePoolPrint();
:打印此时的自动释放池的内容
MacOS Command Line Tools
新建一个MacOS Command Line Tools的项目,总共只有下面这些代码:
1 | #import <Foundation/Foundation.h> |
来看看输出结果:
1 | 2017-07-13 20:21:52.419722 autoreleaseMac[87916:6040616] array count is 2 |
可以发现:使用array
类函数生成的对象不仅被array给强引用,还被注入到了自动释放池;所以他的retainCount为2。这就说明了在Mac下面,貌似这个Autorelease返回值的快速释放机制不起作用
iOS APP
新建一个iOS的项目,来看看iOS的viewDidLoad
方法:
1 | - (void)viewDidLoad { |
来看看打印结果:
1 | 2017-07-13 20:31:29.637 autorelease[89067:6068334] array count is 1 |
在iOS下面这个array对象的retainCount位1,也没有加入到自动释放池。这么看貌似这个快速释放机制起作用了
先别急:让我们取消注释,声明一个__weak变量,再来看看运行结果:
1 | 2017-07-13 20:35:39.510 autorelease[89587:6080048] 0x6080000468a0 // 对象地址 |
可以看到retainCount变成了2,而且也确实是将array对象加入到了自动释放池中。这时候这个快速释放机制貌似又失效了。。
这里的一个完全无关的__weak变量的声明,似乎是这个快速释放机制的开关;有点玄学=。=
后续操作
clang -S
可以通过clang的-S
选项,来看看这些文件生成的汇编代码,或许可以从中看出些端倪。但是我不懂汇编=.=
LinkMap
开启Xcode的Write Link Map File选项,查看可执行文件的构成。
当没有定义__weak变量时:
当有定义__weak变量时:
可以看到在定义了__weak变量时,多了两个函数的调用:
- __Unwind_Resume
- _objc_destroyWeak
所以感觉问题就在这两个函数之中
Stack Overflow
我将该问题放在了Stack Overflow上面了。如果哪位大神碰巧看到了这篇文章,并且还耐心的看到了这里,碰巧还知道答案的话,可以到StackOverflow上面帮我解解惑