最近在复习自动释放池,在ARC下runtime有一个优化objc_autoreleaseReturnValue和objc_retainAutoreleaseReturnValue,但是优化遇上编译器优化,似乎有点问题…不知道算不算苹果自己给自己挖的坑。
背景
现讲述一下大致的背景:(如果很了解autorelease及其优化原理可以直接跳过这一节)
MRC:
在MRC下,本着谁分配谁释放的原则,在方法返回方法内部创建的某个对象的时候,其实是需要释放掉对象的(因为当前方法栈已经结束)。但是矛盾的是如果释放了,那这个方法如果返回这个对象给调用者,调用者又如何取到?
所以,在返回对象的时候,不释放对象,而是把对象加入到 自动释放池 内(其实这里可以理解为,把对象的所有权交给了自动释放池),此时自动释放池持有了这个对象(引用计数为1)。调用者依旧可以拿到这个对象地址,对象依旧有效。
当然调用者自己是否需要持有它,那是调用者的逻辑了,持有与释放相互匹配。
ARC:
在ARC下,需要依旧保持MRC下的“谁分配谁释放”的原则。但是ARC下编译器不允许调用autorelease方法。编译器会给代码的返回值后自动插入autorelease。
runtime优化:
在MRC下,如果调用者和被调用者都是模块内自己调用,那么其实可以忽略掉自动释放池这一个步骤。换句话说,如果可以保证调用者有变量在接收返回值,那么被调用者可以不用加入自动释放池,直接把对象的所有权转移给函数接受者。
ps:当然这样会有风险,前提是模块内调用,如果是模块间调用,没有变量接收返回值的话,那么就会造成内存泄漏了。
那么在ARC下如何优化呢?
runtime结合编译器自动实现了这个过程。其实runtime的优化思路和上面MRC环境下开发者的优化思路是一样的,只不过把这个过程转换为了代码。
在被调用方使用__builtin_return_address(int depth)这个函数判断调用方的接受者是否使用了这个优化函数objc_retainAutoreleaseReturnValue(有两种情况不使用,一种是MRC,一种是没有接受者),如果使用了优化那么被调用方objc_autoreleaseReturnValue就会把某个标记位flag设置到TLS。
如果调用方不使用优化,那么就是走自动释放池的逻辑;如果调用方使用了优化函数objc_retainAutoreleaseReturnValue,那么先去TLS看有没有这个标记位(防止被调用方没有优化),如果有,直接使用返回的值,无需再调用retain了。
简单总结:
如果双方都使用优化,那么就直接用返回的对象,被调用方不释放,调用方不持有。实际就是对象拥有者直接转移。
调用者通过TLS的flag判断被调用者是否使用了优化。
被调用者通过__builtin_return_address(int depth)这个函数判断调用者是否使用优化。
问题
在理解的过程中,我一直以为这个在ARC下的实验结果应该没有悬念的…但现实却给了我狠狠的一巴掌。
我在Xcode里新建了一个 Mac 应用程序的Demo,ARC,方法返回的值始终在自动释放池内。
下面是测试代码,测试代码来自网络..
1 | extern void _objc_autoreleasePoolPrint(void); |
上面这3段代码,区别在于后两者插入了一个__weak指针的赋值,但是实际结果却是三个数组都在自动释放池内。
于是我找到了这篇 文章….
然后我又在真机上调试了一下,确实,第一个没有进自动释放池,后两个都进了。
文章作者最后给的解释比较模糊(后面两个没有调用retain),于是我只能自己再找原因。
解决
Runtime其实已经有源码可寻,。关键在于调用者的那个优化函数的指令地址。
那么就看看汇编代码,到底有什么区别…
下面是ARM(真机)的汇编代码:
在runtime的实现里会直接比较调用者下一个函数在代码内的地址与objc_retainAutoreleasedReturnValue的函数地址。很明显,第一个之所以不在池内是因为后面紧跟着objc_retainAutoreleasedReturnValue,而第二个就不是。
第二个和第一个的区别只有一个weak指针的赋值,于是我把赋值也去掉了。
1 | id __weak obj2; |
结果依旧如此….
??????
我第一反应就想到了编译器的优化。

默认情况下,Debug的优化级别为None,Release的是Fastest,Smallest[-Os]。我把Debug改为和Release一样…
结果……就和预期一样,三个都不会在自动释放池内,也就是说在真机上运行是符合预期的。
编译器不优化会导致 objc_retainAutoreleasedReturnValue和返回函数之间被插入代码,优化了反而不会被插入…
对编译器不了解,这里没有任何发言权,本文也只是一些实验和猜测…
另外x86的iOS模拟器和mac程序,调整优化level也只能部分达到预期…