前言
上篇文章介绍了引用计数和弱引用的源码,这篇文章来探索一下自动释放池和autorelease的源码
autorelease
autorelease的作用是延迟释放对象,源码如下:
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this; // 如果是 tagged pointer 类型,直接返回当前对象
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; // 如果 prepareOptimizedReturn 是True,直接返回当前对象
return rootAutorelease2(); // 前面的操作都没走,则调用 rootAutorelease2
}
拆解一下函数
- prepareOptimizedReturn
prepareOptimizedReturn 是用于优化返回的,其实现如下
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
assert(getReturnDisposition() == ReturnAtPlus0); // 确保当前获取的 Disposition 为 false
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { // 如果当前允许优化返回值
if (disposition) setReturnDisposition(disposition); // 设置 ReturnDisposition 为 true
return true;
}
return false;
}
- rootAutorelease2
rootAutorelease2的实现很简单,就调用了AutoreleasePoolPage的autorelease方法,把对象放入到自动释放池
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
自动释放池(Autoreleasepool)
首先来看下Autoreleasepool的数据结构
接着上面的
AutoreleasePoolPage::autorelease((id)this)
这个入口,从这里开始分析源码
static inline id autorelease(id obj)
{
assert(obj); // 断言,对象不为空
assert(!obj->isTaggedPointer()); // 断言,对象不为 TaggedPointer
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); // 确保 dest 不存在或等于 EMPTY_POOL_PLACEHOLDER 或等于 obj
return obj;
}
- autoreleaseFast
接着往下走,查看autoreleaseFast的源码
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 获取当前最上层的 page
if (page && !page->full()) { // 如果 page 存在且不是满的
return page->add(obj); // 把对象添加到当前的 page
} else if (page) { // 如果 page 存在,但已经满了
return autoreleaseFullPage(obj, page); // 调用 autoreleaseFullPage 方法
} else { // 如果 page 不存在
return autoreleaseNoPage(obj); // 调用 autoreleaseNoPage 方法
}
}
可以看出自动释放池是由一个或多个
AutoreleasePoolPage
组成,接下来继续拆解函数
- hotPage
hotPage方法是用来获取当前最上层的page
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key); // 通过 tls 查询可用 AutoreleasePoolPage 对象
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 如果查询结果为 EMPTY_POOL_PLACEHOLDER,返回 nil
if (result) result->fastcheck(); // 如果 result 不为空,则调用 fastcheck 方法,这里面做了检测线程的工作
return result;
}
key
定义如下:
// Thread keys reserved by libc for our use.
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
- full
full 方法是用来检测当前的AutoreleasePoolPage是否满了,实现如下
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool full() {
return next == end();
}
SIZE的定义如下
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_SIZE I386_PGBYTES
#define PAGE_MAX_SIZE PAGE_SIZE
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
- add
add 功能是把对象添加到AutoreleasePoolPage
id *add(id obj)
{
assert(!full()); // 确保当前状态不满
unprotect(); // 设置为读/写状态
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect(); // 设置为只读状态
return ret;
}
- autoreleaseFullPage
autoreleaseFullPage函数的工作是新建一个AutoreleasePoolPage并且把对象放进去
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child; // 如果 page->child 存在,则给 page 赋值 page->child
else page = new AutoreleasePoolPage(page); // 如果不存在,则新建一个AutoreleasePoolPage
} while (page->full());
setHotPage(page); // 设置新建的 AutoreleasePoolPage 为 hotPage
return page->add(obj); // 把对象添加到新建的 AutoreleasePoolPage
}
AutoreleasePoolPage的构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
- setHotPage
通过tls去设置hotPage
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
- autoreleaseNoPage
如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage,并调用 page->add(obj) 将对象添加到 AutoreleasePoolPage 的栈中
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) { // 如果当前有 poolPlaceholder
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder(); // 设置占位符
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY); // 添加边界符,是一个nil,用来区别每个AutoreleasePoolPage的边界
}
// Push the requested object or pool.
return page->add(obj);
}
- haveEmptyPoolPlaceholder
static inline bool haveEmptyPoolPlaceholder()
{
id *tls = (id *)tls_get_direct(key);
return (tls == EMPTY_POOL_PLACEHOLDER);
}
- setEmptyPoolPlaceholder
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
看完了添加的过程,来看下AutoreleasePoolPage释放的过程。从上面的代码分析过程中我们可以得知AutoreleasePool本质是个双向链表,释放的过程就是从最后一张链表开始删除,逐级往上删,用下图表示:
来看下AutoreleasePoolPage::pop的源码
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// EMPTY_POOL_PLACEHOLDER:当自动释放池为空时的一个占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // 如果传入的值为 EMPTY_POOL_PLACEHOLDER
// Popping the top-level placeholder pool.
if (hotPage()) { // 如果有 hotPage, 即池非空
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin()); // 将整个自动释放池销毁
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil); // 没有 hotPage, 即为空池, 设置 hotPage 为 nil
}
return;
}
page = pageForPointer(token); //根据 token 找到所在的 节点
stop = (id *)token; //token 转换给 stop
if (*stop != POOL_BOUNDARY) { //如果 stop 中存储的不是边界符
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
// 存在自动释放池的第一个节点存储的第一个对象不是边界符的情况, 有两种情况导致:
// 1. 顶层池没释放, 但留下了第一个节点
// 2. 没有自动释放池的 autorelease 对象
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
// 非自动释放池的第一个节点, stop 存储的也不是边界符的情况
return badPop(token); // 调用错误情况下的 badPop()
}
}
if (PrintPoolHiwat) printHiwat(); //如果需要打印 hiwat, 则打印
page->releaseUntil(stop); // 将自动释放池中 stop 地址之后的所有对象释放掉
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) { // 如果 page 有 child 节点
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) { // 如果 page 已占用空间少于一半
page->child->kill(); // kill 掉 page 的 child 节点
}
else if (page->child->child) { // 如果 page 的占用空间已经大于一半, 并且 page 的 child 节点有 child 节点
page->child->child->kill(); // kill 掉 child 节点的 child 节点
}
}
}
- pageForPointer
由于为
AutoreleasePoolPage 对象分配
的地址都是按 4096 对齐
的, 也就是说AutoreleasePoolPage 对象所处的地址都是 4096 的倍数
, 所以 token 转换为十进制数时, 对 4096 取余, 就能得到 token 地址对 AutoreleasePoolPage 对象地址的偏移量. 又因为 AutoreleasePoolPage 对象本身的大小是 56, 所以如果 token 对4096 取余
的结果如果小于 56 就是错误的
, 此时会抛出异常. 否则 token 地址减去偏移量, 就是 AutoreleasePoolPage 对象的地址, 转换为AutoreleasePoolPage * 类型
的指针, 就是该 token 所处的 page 节点。
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE; // 转换为十进制数的 p 余上 4096
assert(offset >= sizeof(AutoreleasePoolPage)); // 如果余数小于 AutoreleasePoolPage 的大小则抛出异常
result = (AutoreleasePoolPage *)(p - offset); // 十进制数 p 减掉刚刚得到的余数 offset, 结果转换为AutoreleasePoolPage * 类型指针
result->fastcheck(); // 根据配置进行 check
return result;
}
- badPop
这个函数正常情况下是调用不到的, 只有使用旧 SDK 的时候有可能会发生. 一旦发生 badPop 时, 会记录下错误日志, 并销毁该自动释放池.
static void badPop(void *token)
{
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
// 对于旧的 SDK 来说, 这个错误并不是致命的
if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0, 2_0)) {
// OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
// 对于开启 pool 内存分配的 debug 模式, 以及最新 SDK 的情况, 调用到 badPop 是错误的
_objc_fatal
("Invalid or prematurely-freed autorelease pool %p.", token);
}
// Old SDK. Bad pop is warned once.
// 旧 SDK 下, Bad pop 会记录一次日志
static bool complained = false;
if (!complained) {
complained = true;
// 输出一系列信息到 crash log 里, 但不会触发 crash
_objc_inform_now_and_on_crash
("Invalid or prematurely-freed autorelease pool %p. "
"Set a breakpoint on objc_autoreleasePoolInvalid to debug. "
"Proceeding anyway because the app is old "
"(SDK version " SDK_FORMAT "). Memory errors are likely.",
token, FORMAT_SDK(sdkVersion()));
}
objc_autoreleasePoolInvalid(token); // 摧毁包含 token 的自动释放池
}
- releaseUntil
自动释放池销毁对象中最重要的一环, 调用者是
用 pageForPointer() 找到的
, token 所在的 page 节点, 参数为 token. 这个函数主要操作流程就是, 从 hotPage 开始,使用 next 指针遍历
存储在节点里的autorelease 对象列表
, 对每个对象进行一次 release 操作
, 并且把 next 指向的指针清空, 如果 hotPage 里面的对象全部清空, 则继续循环向前取 parent 并继续用 next 指针遍历 parent, 一直到 next 指针指向的地址为 token 为止. 因为 token 就在 this 里面, 所以这个时候的 hotPage 应该是 this.
void releaseUntil(id *stop)
{
// 这里没有使用递归, 防止发生栈溢出
while (this->next != stop) { // 一直循环到 next 指针指向 stop 为止
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage(); // 取出 hotPage
while (page->empty()) { // 从节点 page 开始, 向前找到第一个非空节点
page = page->parent; // page 非空的话, 就向 page 的 parent 节点查找
setHotPage(page); // 把新的 page 节点设置为 HotPage
}
page->unprotect(); // 如果需要的话, 解除 page 的内存锁定
id obj = *--page->next; // 先将 next 指针向前移位, 然后再取出移位后地址中的值
memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); // 将 next 指向的内存清空为SCRIBBLE
page->protect(); // 如果需要的话, 设置内存锁定
if (obj != POOL_BOUNDARY) { // 如果取出的对象不是边界符
objc_release(obj); // 给取出来的对象进行一次 release 操作
}
}
setHotPage(this); // 将本节点设置为 hotPage
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
- kill
自动释放池中需要 release 的对象都已操作完成, 此时 hotPage 之后的 page 节点都已经清空了, 需要把这些节点的内存都回收, 操作方案就是从最后一个节点, 遍历到调用者节点, 挨个回收.
void kill()
{
// 这里没有使用递归, 防止发生栈溢出
AutoreleasePoolPage *page = this; // 从调用者开始
while (page->child) page = page->child; // 先找到最后一个节点
AutoreleasePoolPage *deathptr;
do { // 从最后一个节点开始遍历到调用节点
deathptr = page; // 保留当前遍历到的节点
page = page->parent; // 向前遍历
if (page) { // 如果有值
page->unprotect(); // 如果需要的话, 解除内存锁定
page->child = nil; // child 置空
page->protect(); // 如果需要的话, 设置内存锁定
}
delete deathptr; // 回收刚刚保留的节点, 重载 delete, 内部调用 free
} while (deathptr != this);
}
参考文章
从源码角度看苹果是如何实现 autorelease 和 autoreleasepool 的
AutoreleasePool 的实现机制 (四)