iOS学习笔记【四】——Autoreleasepool
只做简单笔记📝 详细请戳标题链接🔗
main函数入口
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@autoreleasepool {}
被转换为一个 __AtAutoreleasePool
结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
这个结构体会在初始化时调用 objc_autoreleasePoolPush()
方法,会在析构时调用 objc_autoreleasePoolPop()
方法,实际都调用了AutoreleasePoolPage
的方法
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage
- 每一个自动释放池都是由一系列的 AutoreleasePoolPage 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节。
- 自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的。
class AutoreleasePoolPage {
magic_t const magic; //用于对当前 AutoreleasePoolPage 完整性的校验
id *next; //指向了下一个为空的内存地址
pthread_t const thread; //保存了当前页所在的线程
AutoreleasePoolPage * const parent; //用来构造双向链表的指针
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
AutoreleasePoolPage的结构栈
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的空间都是用来存储加入到自动释放池中的对象,begin()
和 end()
这两个类的实例方法帮助我们快速获取这一空间。
POOL_SENTINEL(哨兵对象)
#define POOL_SENTINEL nil // nil别名
- 在每个自动释放池初始化调用
objc_autoreleasePoolPush()
的时候,都会把一个POOL_SENTINEL
push 到自动释放池的栈顶,并且返回这个POOL_SENTINEL
哨兵对象。 - 而当方法
objc_autoreleasePoolPop
调用时,就会向自动释放池中的对象发送 release 消息,直到第一个 POOL_SENTINEL。
objc_autoreleasePoolPush 方法
static inline void *push() {
return autoreleaseFast(POOL_SENTINEL);
}
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage(); // 当前正在使用的 AutoreleasePoolPage
if (page && !page->full()) {
return page->add(obj); // 将对象添加至 AutoreleasePoolPage 的栈中
} else if (page) {
return autoreleaseFullPage(obj, page); // 当前 hotPage 已满,查找下一未满page
} else {
return autoreleaseNoPage(obj); // 创建一个 Page
}
}
objc_autoreleasePoolPop 方法
static inline void pop(void *token) {
AutoreleasePoolPage *page = pageForPointer(token); // 获取当前token所在的page
id *stop = (id *)token;
page->releaseUntil(stop); // 释放栈中的对象直到哨兵位置
if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
} else if (page->child->child) {
page->child->child->kill();
}
}
}
autorelease 方法
在自动释放池内调用autorelease方法,最终会调用上面提到的 autoreleaseFast 方法,将当前对象加到 AutoreleasePoolPage 中。
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└── id objc_object::rootAutorelease2()
└── static id AutoreleasePoolPage::autorelease(id obj)
└── static id AutoreleasePoolPage::autoreleaseFast(id obj)
ARC下自动放入AutoreleasePool
- alloc/new/copy/mutableCopy等持有对象的方法,不会加入autoreleasePool;其他不持有对象的方法通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue来判断是否需要加入autoreleasePool,这是编译器的优化。
- iOS5及之前的编译器,关键字__weak修饰的对象,会自动加入autoreleasePool;iOS5及之后的编译器,则直接调用的release,不会加入autoreleasePool。
- id指针和对象指针(id *,NSError **),会自动加上关键字__autorealeasing,加入autoreleasePool。
Thread和AutoreleasePool的关系
- 所有线程都维护有它自己的自动释放池的堆栈结构。新的自动释放池被创建的时候,它们会被添加到栈的顶部,而当池子销毁的时候,会从栈移除。
- 对于当前线程来说,Autoreleased对象会被放到栈顶的自动释放池中。
- 当一个线程停止,它会自动释放掉与其关联的所有自动释放池。
RunLoop和AutoreleasePool的关系
主线程
主线程的NSRunLoop在监测到事件响应开启每一次event loop之前,会自动创建一个autorelease pool,并且会在event loop结束的时候执行drain操作,释放其中的对象。
- App启动后,苹果在主线程RunLoop里注册了两个Observer,第一个Observer监视的事件是Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()创建自动释放池。
- 第二个Observer监视了两个事件BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的池并创建新池。
- Exit(即将退出Loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池。
子线程
如果没有开启RunLoop,是不会自动创建autoreleasePool;但是如果出现autorelease对象,则会自动创建autoreleasePool。
手动添加AutoreleasePool
AutoreleasePool需要手动添加的情况:循环中产生大量临时变量,占用大量内存;编写的不是基于UI框架的程序,例如命令行工具;使用非Cocoa程序创建的子线程。
AutoreleasePool什么时候释放
RunLoop结束/休眠的时候;调用pool的drain()方法
手动添加的pool, 对象 release 时机就是 }
结束
总结
- 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
- 当对象调用 autorelease 方法时,会将对象加入AutoreleasePoolPage 的栈中
- 调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息
- 使用autorelease的好处:不用关心对象释放时间;autorelease的本质:将对象释放时间延迟