前言
-
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
释放对象。
总结
下面还用一张图来总结自动释放池,因为图形的记忆能够更加的深刻。