Autorelease Pool
现在已经是 ARC 时代了,但是了解更多的 Objective-C 的内存管理机制仍然是十分必要的。一直以来我都弄不清楚 autorelease 的原理,后面看了很多资料,才慢慢了解到 autorelease 的原理。
- (void)test { NSString *string = [NSString stringWithFormat:@"liuluoxing"]; NSString *string_weak_ = string; }
下面我们来捋一捋这个变量的内存引用计数的变化:
1.当使用 [NSString stringWithFormat:@"liuluoxing"] 创建一个对象时,这个对象的引用计数为 1 ,并且这个对象被系统自动添加到了当前的 autoreleasepool 中。
2.当使用局部变量 string 指向这个对象时,这个对象的引用计数 +1 ,变成了 2 。
3.当变量出了当前作用域时,局部变量 string 变成了 nil ,所以其所指向对象的引用计数变成 1 。
4.当出了 @autoreleasepool {} 的作用域时,其中的 autoreleased 对象被 release ,对象的引用计数变成 1 。当出了局部变量 string 的作用域,即 viewDidLoad 方法返回时,string 指向了 nil ,其所指向对象的引用计数变成 0 ,对象最终被释放。
看完以上你可能存在一下疑问:
在代码中并没有手动添加 autoreleasepool ,那这个 autoreleasepool 究竟是哪里来的呢?@autoreleasepool {} 的作用域怎么区分?autoreleasepool又是什么时候释放?
这个时候你需要了解一下AutoreleasePoolPage的原理
AutoreleasePoolPage
一个空的 AutoreleasePoolPage 的内存结构如下图所示:
- magic 用来校验 AutoreleasePoolPage 的结构是否完整;
- next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
- thread 指向当前线程;
- parent 指向父结点,第一个结点的 parent 值为 nil ;
- child 指向子结点,最后一个结点的 child 值为 nil ;
- depth 代表深度,从 0 开始,往后递增 1;
- hiwat 代表 high water mark 。
另外,当 next == begin() 时,表示 AutoreleasePoolPage 为空;当 next == end() 时,表示 AutoreleasePoolPage 已满。
了解了AutoreleasePoolPage的原理,你就能很清晰的知道autoreleasepool是怎么划分作用域以及释放的时机。
那么这个autoreleasepool是从哪里来的呢?
NSThread、NSRunLoop 和 NSAutoreleasePool
根据苹果官方文档中对 NSRunLoop 的描述,我们可以知道每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。
Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.
同样的,根据苹果官方文档中对 NSAutoreleasePool 的描述,我们可知,在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain 。我们上面提到的场景 1 中创建的 autoreleased 对象就是被系统添加到了这个自动创建的 autoreleasepool 中,并在这个 autoreleasepool 被 drain 时得到释放。
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
另外,NSAutoreleasePool 中还提到,每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.
弄清楚 NSThread、NSRunLoop 和 NSAutoreleasePool 三者之间的关系可以帮助我们从整体上了解 Objective-C 的内存管理机制,清楚系统在背后到底为我们做了些什么,理解整个运行机制等。
NSRunLoop
iOS的运行时是由一个一个runloop组成的,每个runloop都会执行下图所示的一些步骤:
在系统级级别的线程中的每个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,
所以,一般情况下,每个接受autorelease消息的对象,都会在下个runloop开始前被释放。也就是说,在一段同步的代码中执行过程中,生成的对象接受autorelease消息后,一般是不会在代码段执行完成前释放的。