iOS Autorelease Pool 总结
原本打算将AutoreleasePool和内存管理,Runloop,线程,事件分发放在一块介绍的,因为这些都有着密切的联系,但是考虑到这样的话文章会很长,看得都累,所以还是分开了。
1. AutoReleasePool 是什么
看过iOS内存管理的都知道,iOS内存管理使用的引用计数会在不使用某个对象的时候调用release 方法,将某个对象的引用计数减1,当某个对象当引用计数减到0的时候对象被释放,dealloc被调用。但是某些情况需要延迟释放,最常见的例子比如当你在一个方法中返回一个对象时就需要延迟释放,AutoreleasePool是一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。具体这些对象什么时候释放,在后续的介绍中将会介绍。这部分只要知道AutoReleasePool 是什么就可以了:
Autorelase Pool 提供了一种可以允许你向一个对象延迟发送release消息的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立即被释放掉(例如在一个方法中返回一个对象时),Autorelease Pool 的作用就显现出来了。 |
2.从宏观看AutoReleasePool位于哪个位置
终于可以上图了:
在一个应用中有两种释放池局部释放池和RunLoop释放池,上图中展示的是RunLoop释放池,局部释放池比较简单,就是在autoreleasepool花括号结束的时候释放释放池里面的对象的,所以这里最关键的就是RunLoop释放池
@autoreleasepool { |
RunLoop释放池会在进入主线程Runloop的时候新建一个Autoreleasepool,在从睡眠被唤醒的时候,通过pop后再push清空并重建一个Autoreleasepool,在退出RunLoop的时候pop掉Autoreleasepool
3. AutoReleasePool的内部结构与机制
首先AutoReleasePool是由多个AutoreleasePoolPage构成的双向链表。
每个AutoreleasePoolPage大小为4096字节,被分成两大部分:
AutoreleasePoolPage描述部分,它是由如下字段构成,大小为56字节:
magic 用来校验 AutoreleasePoolPage 的结构是否完整; |
剩余的用来存储加入到自动释放池的对象,它们其实只是指向这些对象的指针,对象还是存储在堆上。
当前正在使用的AutoreleasePoolPage被称为hotPage.
当我们在代码中使用
@autoreleasepool { |
的时候会转化为:
void * atautoreleasepoolobj = objc_autoreleasePoolPush(); |
3.1 objc_autoreleasePoolPush
objc_autoreleasePoolPush 内部很简单就是调用AutoreleasePoolPage类的push方法。
void *objc_autoreleasePoolPush(void) { |
AutoreleasePoolPage::push会往当前使用的hotPage中添加一个POOL_BOUNDARY 自动释放池边界标记,这个标记作为返回值返回,作为pop的参数。
static inline void *push() { |
之后的完自动释放池添加对象最终也是调用autoreleaseFast。
static inline id *autoreleaseFast(id obj) { |
它分三种情况:
- hotPage 不存在:
比如刚开始的时候是没有hotPage的,它会创建一个然后调用setHotPage将新建的page设置为hotpage,如果没有POOL_BOUNDARY,先往AutoreleasePoolPage添加一个POOL_BOUNDARY,然后再将带加入释放池对象的指针添加到AutoreleasePoolPage。
static id *autoreleaseNoPage(id obj) { |
- hotPage 存在并且还没满
调用 page->add(obj) 方法将对象加入当前hotPage中,也就是将对象添加到next指向的位置,然后将next指向下一个空的位置。
id *add(id obj) { |
- hotPage 满了
调用 autoreleaseFullPage(obj, page) 方法,该方法会先查找 hotPage 的 child,如果有则将 child page 设置为 hotPage,如果没有则将创建一个新的 hotPage,之后在这个新的 hotPage 上执行 page->add(obj) 操作
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { |
3.2 objc_autoreleasePoolPop
objc_autoreleasePoolPop
objc_autoreleasePoolPop 里面只是简单调用AutoreleasePoolPage::pop方法,传入的参数一般是一个POOL_BOUNDARY,
void objc_autoreleasePoolPop(void *ctxt){ |
AutoreleasePoolPage::pop 首先会找到POOL_BOUNDARY所在的页面的地址,然后调用releaseUntil,releaseUntil会针对每页的每一项调用memset重置,然后调用objc_release将对应的对象release。最后将当前页面以及子页面全部删除
static inline void pop(void *token) |
3.3 autorelease
当某个对象调用autorelease的时候,最终会调用AutoreleasePoolPage::autorelease
static inline id autorelease(id obj) { |
最终还是调用autoreleaseFast
3.4 AutoReleasePool drain 和 release区别
当我们向自动释放池pool发送 release 消息时,它会向池中的每一个发送了 autorelease 消息的对象发送一条 release 消息,并且自身也会销毁。当向它发送 drain 消息时,只会释放里面的对象,而不会销毁自己。
3.5 什么情况下会将对象放到自动释放池:
- 对象作为方法返回值时候
- 通过类方法创建对象的时候
- 使用如下的便捷语法来建立对象的时候
NSArray *array = @[@"abc",@"def"]; |