前言
-
Autorelease pools提供了一个延迟向对象发送release消息的机制.它避免了当你想要放弃一个对象的所有权,但又不想马上被释放(比如当你从一个方法中返回一个对象)。 -
比如当我们创建一个对象,并把它放入自动释放池,他不会立即被释放,会等到一次
runloop结束或者作用域超出{}再被释放。
autoreleasepool 和runloop的关系
- 主线程的
NSRunLoop在监测到事件响应开启每一次event loop之前,会自动创建一个autorelease pool,并且会在event loop结束的时候执行drain操作,释放其中的对象 - 如果我们的程序创建了大量临时变量,我们可以创建
Autoreleasepool来降低内存峰值。
autoreleasepool和线程的关系
- 每个线程,包括主线程在内都维护了自己的自动释放池堆栈结构
- 新的自动释放池在被创建时,会被添加到栈顶;当自动释放池销毁时,会从栈中移除
- 对于当前线程来说,会将自动释放的对象放入自动释放池的栈顶;在线程停止时,会自动释放掉与该线程关联的所有自动释放池
clang分析
新建一个项目如下:
进入
main.m 文件所在的目录,输入clang -rewrite-objc main.m -o main.cpp 会生成一个c++文件
@autoreleasepool底层是一个结构体- 在
{}作用域内会调用objc_autoreleasePoolPush()构造函数和objc_autoreleasePoolPop()析构函数
继续添加一个objc_autoreleasePoolPush的符号断点:发现这个源码的实现在objc里面
autoreleasepool结构分析
打开objc源码,搜索objc_autoreleasePoolPush,
发现调用了
AutoreleasePoolPage类里面的push方法,我们在分析源码的时候不要急于去看,我们应该先去了解底层结构.
结构分析
AutoreleasePoolPage继承自AutoreleasePoolPageData
magic:用来检测AutoreleasePoolPage的结构是否完整,占用16位next:指向最新添加的autoreleased对象的下⼀个位置,初始化时指向begin(),占用8位thread:指向当前线程 //占用8位parent:指向⽗结点,第⼀个结点的parent值为nil占用8位child:指向⼦结点,最后⼀个结点的child值为nil占用8位depth:代表深度,从0开始,往后递增1占用4位hiwat:最大入栈标记数量 占用4位
通过注释理解结构
- 一个线程的
autorealse pool是一堆指针 - 这个指针分两种一种是即将要释放的对象,一种是池的边界(POOL_BOUNDARY)。通过边界,我们可以知道page的对象是否被删除完。
- 这个指针的集合结构实际上是一个
双链表结构。pages之间互相关联. - 当前线程本地存储着指向新自动释放的热点页面对象
添加object流程
autoreleaseFast
如果是非bug模式会调用autoreleaseFast
autoreleaseFast调用
- 如果
page存储在并且没有满,直接在page后添加 - 如果
page满了调用autoreleaseFullPage - 如果没有
page调用autoreleaseNoPage
autoreleaseNoPage流程
- 新建一个page,设置为hotpage
- 为
page添加POOL_BOUNDARY边界 page添加obj对象 ,一个page可以看成一个单链表,page->add相当于在链表后面添加一个obj对象。
autoreleaseFullPage流程
- 这个其实就是找到双链表的最后一个
page,退出循环,page添加对应的obj
page的大小
在源码里面有一个宏定义
-
所以一页page大小是
1<<12,也就是4096. -
但是一页page能够存储多少个对象呢?,page本身属性大小 =magic+next+thread+parent+child+depth+hiwat=56,如果是首页还有一个边界对象POOL_BOUNDARY占用8个字节。 -
所以首个page,对象数量
objcCount = (4096-56-8)/8 = 504, -
如果不是首页没有边界,那就是
505个对象
验证page大小
_objc_autoreleasePoolPrint可以打印page的信息,上面生成了506个局部变量,打印结果如下
- 总共有两页,第一页已经满了有504个对象,第二页有2个
AutoreleasePool的嵌套
很好奇如果AutoreleasePool嵌套,page的结构会是怎样的,做如下测试
查看打印结果:
- 当第一个自动释放池的
page没有放满的时候,嵌套的自动释放池的对象会继续往后面添加。 - 只要创建一个自动释放池,就会创建一个边界对象。
自动释放池的释放
调用_objc_autoreleasePoolPop方法
pop里面重点的方法就是调用popPage,其他的代码都是调试相关的,不影响主流程
进入popPage
releaseUntil
重点方法就是调用page->releaseUntil方法不断的调用objc_release(obj)给obj发送release消息。
- 它的实现还是很容易的,用一个
while循环持续释放AutoreleasePoolPage中的内容,直到next指向了stop - 使用
memset将内存的内容设置成SCRIBBLE,然后使用objc_release释放对象。
总结
下面还用一张图来总结自动释放池,因为图形的记忆能够更加的深刻。