目前我们内存管理都在在ARC环境下进行自动管理对象的引用计数,我们下面看下autoreleasepool。
1.autoreleasepool
1.1 release与autorelease
对象在调用release方法的时候引用计数会立刻减1。
而对象在调用autorelease方法时,对象的引用计数会在稍后的时间内减1,通常是在当前线程下一个“事件循环”。autorelease方法在方法返回对象的时候特别有用。如果方法中创建的对象在返回前执行release方法,那有可能对象还没有返回就被销毁了。使用autorelease方法,可以保证对象在返回的时候依然存活,也无需调用者手动释放对象,因为在当前线程的下一个事件循环,对象的引用计数会自动减1.autorelease方法可以延长对象的生命周期,可以跨越方法调用边界之后存活一段时间。
1.2 自动释放池
1、释放对象有两种方式,一种是调用release方法,调用release方法的对象引用计数会立刻递减;另一种是调用autorelease方法,将对象加入到“自动释放池”中,自动释放池中的对象会在稍后的某个时候被释放,清空自动释放池时系统会向池中的对象发送release消息。
2、自动释放池创建于左花括号,并于右花括号处自动清空,花括号范围内的对象,会于花括号的末尾处收到release消息。
3、自动释放池可以降低应用程序的内存峰值。
4、我们不需要担心自动释放池的创建问题。主线程或者GCD创建的线程,默认都有自动释放池,每一次执行事件循环的时就会将其清空
1.3 Clang分析
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
在main函数会把我们的代码加入autoreleasepool中,Clang结果如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2f_kt02d2w10p55r2z7mn5gc7qw0000gn_T_main_904163_mi_0);
}
return 0;
}
/*析构*/
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
autoreleasepool相当于一个对象,本质上。
__AtAutoreleasePool是一个结构体,有构造函数 + 析构函数,结构体定义的对象在作用域结束后,会自动调用析构函数- 其中
{}是 作用域 ,优点是结构清晰,可读性强,可以及时创建销毁
1.4 AutoreleasePoolPage
看下autoreleasepool的源码描述
我们在clang的时候知道,自动释放池是一个析构函数,
构造的时候调用:
objc_autoreleasePoolPush()析构的时候调用objc_autoreleasePoolPop()
自动释放池调用是通过
AutoreleasePoolPage进行进行push和pop的。
其中页的大小为4096
AutoreleasePoolPage是一个class,有一些方法和自己的析构函数,对页的一些操作大致如下:
AutoreleasePoolPage继承于AutoreleasePoolPageData,我们看下
-
magic用来校验 AutoreleasePoolPage 的结构是否完整; -
next指向最新添加的 autoreleased 对象的下一个位置,初始化时指begin() ; -
thread指向当前线程; -
parent指向父结点,第一个结点的 parent 值为 nil ; -
child指向子结点,最后一个结点的 child 值为 nil ; -
depth代表深度,从 0 开始,往后递增 1; -
hiwat代表 high water mark 最大入栈数量标记 发现其中有AutoreleasePoolPage对象,所以有以下一个关系链AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage,从这里可以说明自动释放池除了是一个页,还是一个双向链表结构。
2. objc_autoreleasePoolPush
判断当前
是否有释放池,没有的话创建一个,有的话压栈一个POOL_BOUNDARY.
- 继续看下
autoreleaseNewPage
- 查看
hotPage()
- 查看
autoreleaseNoPage创建page
- 查看初始化自动释放池页
AutoreleasePoolPage
其中
begin()表示压入对象开始的位置,表示AutoreleasePoolPage自己占有56大小
- 查看自动释放池内存结构
在ARC环境下,无法手动调用
autorelease,将Demo切换至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting设置为NO)
我们for循环压入打印自动释放池情况
页的地址0x7fbcc2810000和边界地址0x7fbcc2810038相差16进制下3*16+8 =56,刚好是
AutoreleasePoolPage自己本身的内存大小。
我们加入505个对象
第一页存储了
504个对象,第二页存储了1个对象,第二页不再存储边界哨兵了。我们继续for循环1010个对象。
第一页
第二页
第三页
增加了
505个对象,增加了一页,说明一页可以存储505个对象,大小为505*8 = 4040大小,页本身大小为56 所以一页的大小为4096和红定义一样。首页因为存储了哨兵对象所以少存储一个对象。
- 压栈对象
autoreleaseFast
进入
autoreleaseFast源码,主要有以下几步:
- 获取当前操作页,并判断页是否存在以及是否满了
- 如果页
存在,且未满,则通过add方法压栈对象 - 如果页
存在,且满了,则通过autoreleaseFullPage方法安排新的页面 - 如果
页不存在,则通过autoreleaseNoPage方法创建新页
autoreleaseFullPage分析
遍历当前页的子页面链,直到没有子页面创建一个页面- 设置创建的页为
hotPage操作页面 - 向创建的页添加对象,
压栈。
autorelease底层分析autorelease->rootAutorelease->rootAutorelease2->autorelease(id obj)
最终还是执行
autoreleaseFast进行压栈操作,和压栈哨兵对象一样进入autoreleaseFast只是对象不同。
3. objc_autoreleasePoolPop
在objc_autoreleasePoolPop方法中有个参数,在clang分析时,发现传入的参数是push压栈后返回的哨兵对象,即ctxt,其目的是避免出栈混乱,防止将别的对象出栈
查看pop源码
- 空页面的处理,并
根据token获取page - 容错处理
- 通过
popPage出栈页
popPage查看
不是哨兵对象的话通过
popPage<false>(token, page, stop)出栈操作,通过releaseUntil出栈当前操作页面对象,释放到stop位置之前的所有对象,之后对当前页进行判断是否为空,进行相关页kill。
releaseUntil查看
通过while循环遍历出当前stop的对象之前所有的object,哨兵除外进行
objc_release出栈操作,其中嵌套一个while循环,不断向前父节点页遍历。
进入
releaseUntil
之后从后面的页,一页一页往前进行
release页中的对象,直到stop对象,不是哨兵对象就进行release。
4. 总结
1.自动释放池主要是为我们管理对象内存,不用我们手动的进行release操作,而是由释放池在下次事件循环时候把释放池中对象进行release操作,会延缓对象的生命周期。
2. 自动释放池是一个对象,自身有一个析构函数,创建的时候进行压栈操作push,析构的时候进行出栈操作pop。继承于autorelseasePoolPage类,poolPage本身继承于结构体autoreleasePoolPageData,大小为56字节。创建page后会插入一个POOL_Boundary(哨兵对象)主要是为了防止越界,但是只有首页会插入哨兵对象。一页最多可插入505个对象,页的大小为505*8+56(页本身大小) = 4096字节,poolPage是一个双向链表的结构,它有一个父节点一个子节点,首页的父节点为空,末页的子节点为空。
3.autorelseasePoolPage加入对象通过push进行操作,没有页的话创建页同时传入哨兵对象,这一步在构造的时候就会调用。之后页满了的话通过autoreleaseNoPage创建新的页,通过add的方法加入对象。
4. 通常在析构的时候调用pop方法,会传入哨兵对象,我们根据stop的位置,释放stop对象之前所有的对象,哨兵除外。通过while循环当前页的---next往上依次release对象,当前页空的话就把hotpage设置为父节点页面,重复之前操作直到stop对象。