Autorelease Pool
回收机制
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟
加入AutoreleasePool中的变量release的时机
创建和释放 时机
接下来从main函数入口看下整个流程:
main 函数入口
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
从代码上看,整个主线程都被包含在 autoreleasepool 大括号内
因此,主线程上的对象,都在自动回收机制内,不会出现内存泄漏,不需要 手动
release了
创建主线程
接着 UIApplicationMain 内部开始创建一条主线程
Runloop
接着主动访问runloop,接着主线程的runloop 开始被创建
Runloop 之 Observer
RunLoop 里注册了两个 Observer,其回调都是
_wrapRunLoopWithAutoreleasePoolHandler()
第一个 Observer
监视的事件是 Entry(即将进入Loop)
调用回调:
_wrapRunLoopWithAutoreleasePoolHandler()
回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池 autoreleasepool
push()优先级最高,回调中其它回调都在push结束后执行,确保代码块都在auto
release pool 范围内
第二个 Observer
第一个监听: BeforeWaiting(准备进入休眠)
调用_objc_autoreleasePoolPop() 释放旧的池
调用pop()后,释放池开始释放对象
接着调用_objc_autoreleasePoolPush() 并创建新池
第二个监听: Exit(即将退出Loop)
调用 _objc_autoreleasePoolPop() 来释放自动释放池
优先级最低,确保退出之前
释放工作全部完成
不再创建新的池
小结:
线程 依赖Runloop 维持生命,一一对应。
而Runloop又依赖 一个主 autoreleasePool 来管理对象 , 也是一一对应
开发者也可以在主 autoreleasePool 中,嵌套更多的子 autoreleasePool 来管
理局部代码块的释放时机,比如for循环中临时对象释放
至此,释放池创建与释放逻辑基本完成,子线程类似,只是Runloop 需要开发者
主动获取
实现原理
从上面结构中可以看到,AutoreleasePool 是AutoreleasePoolPage ,一个 C+
+ 中的类
一次流程: push() --> [objs release] --> pop(obj)
Push()
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
Pop(void *)
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 到底如何实现的?
结构
AutoreleasePoolPage {
magic_t const magic; //magic用来校验Page结构是否完整;
id *next; //指向新加入的autorelease对象
pthread_t const thread; //thread指向当前的线程
AutoreleasePoolPage * const parent; //父节点 指向前一个page
AutoreleasePoolPage *child; //子节点 指向下一个page
uint32_t const depth; //链表的深度,节点个数
uint32_t hiwat; //数据容纳的一个上限
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE = PAGE_MAX_SIZE; //虚拟内存每个扇区4096个
字节,4K对齐
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE // 一个page里对象数
}
双向链表
AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双
向链表的形式组合而成的栈结构(分别对应结构中的parent指针和child指针)
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage
对象,连接链表,后来的autorelease对象在新的page加入
示意图
Page 1
指针
parent -->page0
child -->page2
Page 2
指针
parent -->page1
child -->page3
Page 3
指针
parent -->page2
child -->page4
等等
Push 流程
push() 代码
Runloop 回调:_wrapRunLoopWithAutoreleasePoolHandler()
调用 objc_autoreleasePoolPush()
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
push()
static inline void *push() {
return autoreleaseFast(POOL_BOUNDARY);
}
autoreleaseFast :
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
hotPage() 就是指当前 page
autoreleaseFast(id obj) 中的id 是 push生成的边界对象
push() 逻辑
【无 hotPage】
调用 autoreleaseNoPage 创建一个 hotPage,调用 page->add(obj) 方法将对
象添加至 AutoreleasePoolPage 的栈中
【有 hotPage 并且当前 page 不满】
调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
【有 hotPage 并且当前 page 已满】
调用 autoreleaseFullPage 初始化一个新的页,调用 page->add(obj) 方法将
对象添加至 AutoreleasePoolPage 的栈中
假设右侧嵌套结构
@AutoreleasePool { // 1层
obj1, obj2;
@AutoreleasePool { // 2层
obj3, obj4
@AutoreleasePool { // 3层
obj5, obj6
}
}
}
嵌套push()
示意图:
高地址
--------
Page 4Kb
--------
低地址
push
高地址
--------
Page 4Kb
obj2
obj1
POOL_BOUNDARY(边界对象)
--------
低地址
push
高地址
--------
Page 4Kb
obj4
obj3
POOL_BOUNDARY
obj2
obj1
POOL_BOUNDARY
--------
低地址
简单说就是每次push都会从低地址向高地址:
插入边界 - 插入obj - 插入obj2 - , 如果一页放不下了,就新建一页
嵌套pop(obj)
释放就简单了,从高地址的obj 开始释放,直到 边界对象为止,这样一个pool
释放完成
该过程主要分为两步:
- page->releaseUntil(stop),对栈顶(page->next)到stop地址
(POOL_SENTINEL)之间的所有对象调用objc_release(),进行引用计数减1
- 清空page对象page->kill(),有两句注释