内存管理一直是学习 Objective-C 的重点和难点之一,尽管现在已经是 ARC 时代了,但是了解 Objective-C 的内存管理机制仍然是十分必要的。其中,弄清楚 autorelease 的原理更是重中之重,只有理解了 autorelease 的原理,我们才算是真正了解了 Objective-C 的内存管理机制。注:本文使用的 runtime 源码是当前的最新版本 objc4-646.tar.gz 。
autoreleased 对象什么时候释放
autorelease 本质上就是延迟调用 release ,那 autoreleased 对象究竟会在什么时候释放呢?为了弄清楚这个问题,我们先来做一个小实验。这个小实验分 3 种场景进行,请你先自行思考在每种场景下的 console 输出,以加深理解。注:本实验的源码可以在这里 AutoreleasePool 找到。
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 33 34 |
|
思考得怎么样了?相信在你心中已经有答案了。那么让我们一起来看看 console 输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
跟你预想的结果有出入吗?Any way ,我们一起来分析下为什么会得到这样的结果。
分析:3 种场景下,我们都通过 [NSString stringWithFormat:@leichunfeng] 创建了一个 autoreleased 对象,这是我们实验的前提。并且,为了能够在 viewWillAppear 和 viewDidAppear中继续访问这个对象,我们使用了一个全局的 __weak 变量 string_weak_ 来指向它。因为 __weak 变量有一个特性就是它不会影响所指向对象的生命周期,这里我们正是利用了这个特性。
场景 1:当使用 [NSString stringWithFormat:@leichunfeng] 创建一个对象时,这个对象的引用计数为 1 ,并且这个对象被系统自动添加到了当前的 autoreleasepool 中。当使用局部变量 string 指向这个对象时,这个对象的引用计数 +1 ,变成了 2 。因为在 ARC 下 NSString *string 本质上就是 __strong NSString *string 。所以在 viewDidLoad 方法返回前,这个对象是一直存在的,且引用计数为 2 。而当 viewDidLoad 方法返回时,局部变量 string 被回收,指向了 nil 。因此,其所指向对象的引用计数 -1 ,变成了 1 。
而在 viewWillAppear 方法中,我们仍然可以打印出这个对象的值,说明这个对象并没有被释放。咦,这不科学吧?我读书少,你表骗我。不是一直都说当函数返回的时候,函数内部产生的对象就会被释放的吗?如果你这样想的话,那我只能说:骚年你太年经了。开个玩笑,我们继续。前面我们提到了,这个对象是一个 autoreleased 对象,autoreleased 对象是被添加到了当前最近的 autoreleasepool 中的,只有当这个 autoreleasepool 自身 drain 的时候,autoreleasepool 中的 autoreleased 对象才会被 release 。
另外,我们注意到当在 viewDidAppear 中再打印这个对象的时候,对象的值变成了 nil ,说明此时对象已经被释放了。因此,我们可以大胆地猜测一下,这个对象一定是在 viewWillAppear 和 viewDidAppear 方法之间的某个时候被释放了,并且是由于它所在的 autoreleasepool 被 drain 的时候释放的。
你说什么就是什么咯?有本事你就证明给我看你妈是你妈。额,这个我真证明不了,不过上面的猜测我还是可以证明的,不信,你看!
在开始前,我先简单地说明一下原理,我们可以通过使用 lldb 的 w