本文章基于 objc4-725 进行测试
objc4的代码可以在 https://opensource.apple.com/tarballs/objc4/ 中得到
事情的起因: ARC下 的 @autoreleasepool
之前学习到自动内存管理下 @autoreleasepool {} 的用法, 这两天突然想起来, 又测试了一下, 写了如下的代码:
__weak NSArray * weakArray;
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 100; ++i) {
{
NSArray * array = [NSArray arrayWithObject:@"123"];
weakArray = array;
}
NSLog(@"%@", weakArray);
}
}
不过结果有点出乎了我的预料, 输出全部是 null, 说好的工厂方法返回 autorelease 属性的对象呢?
然后不知道为什么, 我把 weak 属性的对象 weakArray, 放到了 viewDidLoad 里面, 神奇的事情就发生了, 输出就都变成了 123.
于是就想了解一下 ARC, 想知道 ARC到底为返回值做了些什么. 目前得到了一个初步的认识, 想先分享给大家, 至于为什么全局的 weak 指针最后输出是 nil, 局部的 weak 指针最后输出有值这个原因, 是编译器优化的结果, 最终会在编译器优化部分解释.
经过后续测试, 如果使用 NSArray * array = [NSArray arrayWithObjects:@"123", nil] 初始化, 则本例中的编译器优化不会触发.
ARC 自动为返回值添加的 autorelease 相关操作
本部分是未进行编译器优化时的操作.
测试了很久, 试了很多就不一一列举了, 直接上结果.
- (void)test {
__weak NSObject * weakObject;
{
NSObject * object = [self object];
weakObject = object;
}
NSLog(@"%@", weakObject);
}
- (NSObject *)object {
return [[NSObject alloc] init];
}
我这里最终输出了
2018-12-07 10:10:03.276297+0800 iOS-Test-Demo[62002:4059722]
接下来我在网络上找到了一个将 .m 文件编译成 llvm 中间语言的一个命令:
sudo xcrun -sdk iphonesimulator clang -S -fobjc-arc -emit-llvm TestObject.m
执行过这个命令后, 我得到了一个叫做 TestObject.ll 的文件, 这个文件里面是一种类似于汇编的语言. 我截取了最重要的部分, 并把它写成伪代码的形式便于观察:
- (void)test {
__weak NSObject * weakObject;
{
NSObject * ret = objc_msgSend(self, @selector(object)); //调用object返回 autorelease 类型的 ret
NSObject * object = objc_retainAutoreleasedReturnValue(ret); //将 ret 进行一次 retain 并赋值给 object
objc_storeWeak(weakObject, object); //将 object 直接赋值给 weakObject
objc_storeStrong(object, null); //出作用域之前的置空
}
NSLog(@"%@", weakObject);
//结束后等 autoreleasepool 回收
}
- (NSObject *)object {
NSObject * object = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(object, @selector(init));
NSObject * ret = objc_autoreleaseReturnValue(object); //返回之前, 用该方法加入到了 autoreleasepool 中
return ret;
}
objc_autoreleaseReturnValue() 和 objc_retainAutoreleasedReturnValue() 是成对出现的, 前者将对象加入到 autoreleasepool, 后者将 autorelease 类型对象 retain. 初始化的对象引用计数是1, retain 后变为2, 所以 object 出作用域的时候, weakObject 还可以继续使用.
以上就是编译期为返回值做的 autorelease 相关的处理, 另外 init 和 copy 开头的函数, 不会进行这样的处理.
编译器优化
返回值优化主要靠 objc_autoreleaseReturnValue() 和 objc_retainAutoreleasedReturnValue() 两个函数, 而编译器优化也是针对这两个方法进行的优化. 我们看一下这两个函数的源码:
// 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);
}
// 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);
}
可以看到两个函数均有一个判断, 这个判断就是编译器优化的关键.
objc_autoreleaseReturnValue() 和 objc_retainAutoreleasedReturnValue() 两个函数成对出现就是因为各自内部调用的 prepareOptimizedReturn() 和 acceptOptimizedReturn(), 前者是提供一个优化过的返回值, 后者是判断该返回值是否优化过了.
prepareOptimizedReturn() 有 bool 类型的返回值, 代表是否优化完成. 还有一个 ReturnDisposition 类型的枚举:
enum ReturnDisposition : bool {
ReturnAtPlus0 = false, ReturnAtPlus1 = true
};
代表优化设置, ReturnAtPlus0 即为优化时引用计数 +0, 而 ReturnAtPlus1 即为优化时引用计数 +1.
本次测试的例子, prepareOptimizedReturn() 是以 ReturnAtPlus1 为优化设置.
优化成功后调用 acceptOptimizedReturn() 将会返回对应的优化设置, 也就是 ReturnAtPlus1, 此时 objc_retainAutoreleasedReturnValue() 函数会直接返回 obj 不会对其 retain.
优化失败则 acceptOptimizedReturn() 将会返回 ReturnAtPlus0, 此时 objc_retainAutoreleasedReturnValue() 函数会对 obj 进行retain.
另外
// 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;
}
prepareOptimizedReturn() 函数中主要通过 callerAcceptsOptimizedReturn() 函数来判断是否需要或者是否可以进行优化, 该函数的参数 __builtin_return_address(0)) 代表当前函数地址. 这个函数涉及更底层的东西, 目前还没有去接触.
但是通过对该函数的注释, 大致得出了这个函数的实现就是通过一系列对帧指针寄存器的判断来判断该地址指向的函数是否可进行优化, 还有优化的方案叫做 Tread Local Storage, TLS, 线程本地存储, 是线程对应的一块地址空间, 对返回值的优化就是将返回值存入到该段空间中, 避免了加入到 autoreleasepool 中的一系列操作.