定义
NSAutoreleasePool
是用来做引用计数管理的,当一个对象收到autorelease
消息的时候,这个对象就会被放到NSAutoreleasePool
中。当NSAutoreleasePool
被销毁的时候,NSAutoreleasePool
向它包含的每一个对象发送release
消息,也就是说调用autorelease
并不会立马销毁对象,这样就延长了这个对象的生命周期。
用法
autorelease pool
的用法如下:
1 | @autoreleasepool { |
等同于
1 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
Cocoa建议在autorelease pool中执行我们的代码,否则造成一个autorelease的对象无法释放导致内存泄露。AppKit和UIKit会在每次event runloop进行的开始创建autorelease pool,在runloop结束的时候会释放autorelease pool,所以我们一般不需要自己创建一个autorelease pool。
但是还是需要自己创建autorelease pool的场景有如下三种:
- 不是基于UI Framework的程序,例如命令行程序。
- 创建了其他线程,而且要创建子该线程启动之前,否则会造成内存泄露。
- 在循环中创建了很多临时对象,特别是比较占内存的object,这种情况使用runloop会大大减少内存占用。典型的例子是读取大量图像的同时对图像进行压缩,图像文件读入到NSData对象,并从中生成UIImage对象,改变该UIImage的尺寸之后生成新的UIImage对象,这种情况下会成成大量的autorelease的对象,这个时候内存会暴涨。
1 | for(...){ |
由于大量的autorelease对象没有得到释放,在for循环中内存会暴涨,特别是在内存受限的场景例如Share Extension(内存限制在100M),在这种情况下就很有必要在合适的地方创建自己的autoreleasepool。
1 | for(...){ |
这种方式会极大的减少内存的占用,因为每次循环都会释放autoreleasepool的block中产生的临时对象。
@autoreleasepool的本质
代码
1 | int main(int argc, const char * argv[]) { |
用clang -rewrite-objc main.m进行转换,得到如下:
1 | int main(int argc, const char * argv[]) { |
其中__AtAutoreleasePool的定义如下:
1 | struct __AtAutoreleasePool { |
__AtAutoreleasePool在创建时会执行objc_autoreleasePoolPush,在被销毁的时候会执行objc_autoreleasePoolPop(atautoreleasepoolobj)
很明显@autoreleasepool{}被转换成了
1 | { __AtAutoreleasePool __autoreleasepool; |
也就是说在该代码块开始执行的时候会创建结构体__autoreleasepool也就是执行:
1 | void *atautoreleasepoolobj = objc_autoreleasePoolPush(); |
在改代码块结束执行结束的时候会销毁结构体__autoreleasepool,也就是执行
1 | objc_autoreleasePoolPop(atautoreleasepoolobj); |
所以@autoreleasepool{}也就相当于:
1 | void *atautoreleasepoolobj = objc_autoreleasePoolPush(); |
在NSObject的源码中可以看到如下定义:
1 | void * objc_autoreleasePoolPush(void) |
AutoreleasePoolPage的结构
- AutoreleasePool是由若干AutoreleasePoolPage以双向链表的形式组合而成的结构(parent、child)。
- thread 指向当前页所在线程。
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。
- AutoreleasePoolPage用栈结构来存储block中的临时对象地址,next指向了下一个将要加入到栈顶的内存地址。当栈满之后会开辟新的page来继续添加。
代码分析
哨兵(POOL_BOUNDARY)
每当AutoreleasePoolPage::push()调用的时候向stack中添加一个哨兵对象(POOL_BOUNDARY),并将该哨兵对象返回。
AutoreleasePoolPage::push
1 | static inline void *push() { |
当AutoreleasePoolPage::pop(ctxt);调用的时候会将比ctxt(哨兵对象,也就是__AtAutoreleasePool中保存的atautoreleasepoolobj)后面加入的对象释放(When the pool is popped, every object hotter than the sentinel is released)。
AutoreleasePoolPage::autoreleaseFast
1 | static inline id *autoreleaseFast(id obj) |
NSObject::autorelease
然后我们知道在ARC下,我们生成的对象会自动调用autorelease。autorelease在NSObject.mm中的定义为
1 | - (id)autorelease { |
AutoreleasePoolPage中autorelease的定义如下:
1 | static inline id autorelease(id obj) |
可以知道@autoreleasepool{}中创建的对象都会被加入到AutoreleasePoolPage的栈中,AutoreleasePoolPage的栈中只有两种对象一种是POOL_BOUNDARY,一种是在@autoreleasepool{}创建的临时对象。
AutoreleasePoolPage::pop
1 | static inline void pop(void *token) |
由上文可以当autoreleasepool的block结束时会先调用
1 | objc_autoreleasePoolPop(atautoreleasepoolobj) |
这里的atautoreleasepoolobj对象就是哨兵对象POOL_BOUNDARY,当然这里也可以理解为任何对象。
1、pop会根据传入的对象拿到其所在的page。
2、然后调用releaseUntil释放token以及比其晚入栈的对象。
3、然后会将空的page给销毁掉。
TLS(Thread Local Storage)
比较有趣的是page是用tls来进行存储的,hotPage使用tls_get_direct来获取当前页,tls中将一块内存作为某个线程专有的存储,以key-value的形式进行读写的,这里和Java中的ThreadLocal是一样的道理。
1 | static inline AutoreleasePoolPage *hotPage() |
参考:
https://developer.apple.com/documentation/foundation/nsautoreleasepool#//apple_ref/occ/cl/NSAutoreleasePool
http://www.jianshu.com/p/32265cbb2a26
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/