iOS底层探索 --- AutoReleasePool

AutoReleasePool 自动释放池

AutoReleasePool是OC的一种内存自动回收机制,它可以将加入AutoReleasePool变量的release时机 --- 延迟
当我们创建一个对象的时候,正常情况下,变量会在超出其作用域的时候立即release。如果将对象加入到自动释放池中,这个对象不会立即释放,而是等到runloop休眠 或者 超出autoreleasepool作用域{}之后才会被释放

自动释放池

  • 1、从程序启动到加载完成,主线程对应的RunLoop会处于休眠状态,等待用户交互来唤醒RunLoop
  • 2、用户的每一次交互都会启动一次RunLoop,用于处理用户的所有点击,触摸事件等。
  • 3、RunLoop在监听到交互事件之后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池。
  • 4、在一次完整的RunLoop结束之前,会向自动释放池中所有的对象发送release消息,然后\color{red}{销毁}自动释放池。

在大致了解自动释放池的工作流程之后,我们一起来探索一下自动释放池。
在日常的开发中,我们见到的最多的自动释放池就是main函数里面的自动释放池。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
*******
在这个@autoreleasepool Block 中,只包含了一行代码,这行代码将所有的事件、消息全部交给了`UIApplication`来处理。
⚠️⚠️⚠️ 注意:整个 iOS 的应用都是包含在一个自动释放池 Block 中的。

下面我们为了减少干扰代码,将自动释放池中的代码删除,只保留自动释放池。然后将main.m文件转cpp来探索一下,就像我们探索Block一样(Block 底层原理(一)

int main(int argc, char * argv[]) {
    @autoreleasepool {
    }
}

**************
$ clang -rewrite-objc main.m

main.cpp文件中我们可以看到:

struct __AtAutoreleasePool {
    ///构造函数
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    ///析构函数
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    }
}

我们会发现,main.m里面的@autoreleasepool {}变成了__AtAutoreleasePool __autoreleasepool。而__AtAutoreleasePool又是一个结构体;所以自动释放池是一个\color{orange}{结构体对象}
仔细观察__AtAutoreleasePool这个结构体,会发现结构体两个函数:
1、构造函数 objc_autoreleasePoolPush() ,会在结构体初始化的时候调用;
2、析构函数 objc_autoreleasePoolPop(),还在结构体析构的时候调用(也就是说在出了作用域后,会自动调用析构)。
看到两个函数,不知道大家有什么想法,给我的感觉就是,这个一定跟\color{orange}{栈}有关,大家仔细品一品这两个函数名。

到这里,我们好像已经将自动释放池的面纱揭开了一点点,下面我们顺着这个思路继续探索。


源码探索

上面我们看到了连个函数,我们在源码中找这个连个函数是下面的样子:

void *objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

我们会发现,两个函数中,都用到了AutoreleasePoolPage,这两个函数就是对AutoreleasePoolPage对应的静态方法push&pop的封装。
那么我们就再去寻找一下AutoreleasePoolPage

class AutoreleasePoolPage : private AutoreleasePoolPageData
{.......}
///继续跟进
👇👇
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};
  • magic 用来校验 AutoreleasePoolPage 的结构是否完整;
  • next 指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
  • thread 指向当前线程;
  • parent 指向父结点,第一个结点的parent值为nil;
  • child 指向子结点,最后一个结点的child值为nil;
  • depth 代表深度,从 0 开始,往后递增 1;
  • hiwat 代表 high water mark 最大入栈数量标记。(这个地方验证上面的猜想,自动释放池一定跟栈有关。)

每一个自动释放池都是由一系列AutoreleasePoolPage组成的,并且每一个AutoreleasePoolPage的大小都是4096字节(16进制0x1000)

#define I386_PGBYTES            4096            /* bytes per 80386 page */
#define PAGE_SIZE               I386_PGBYTES
  • 双向链表
    自动释放池中的AutoreleasePoolPage是以双向链表的形式连接起来的,这一点我们通过AutoreleasePoolPage里面的参数parent & child就可以知道。
    • AutoreleasePoolPageData结构体的内存大小为56字节:
      • 属性magic的类型是magic_t结构体,所占内存大小为m[4] --- 4*4 = 16字节
      • 属性next(指针)thread(对象)parent(对象)child(对象)均占8字节,所以8*4 = 32字节
      • 属性depthhiwat类型为uint32_t,实际类型为unsigned int类型,均占4字节,所以4*2 = 8字节

自动释放池中的栈
  • POOL_BOUNDARY(哨兵对象)
    在我们接着源码分析之前,先来了解一个感念:POOL_BOUNDARY(哨兵对象)
    哨兵对象只是nil的别名:
#   define POOL_BOUNDARY nil

在每个自动释放池初始化调用objc_autoreleasePoolPush的时候,都会把一个POOL_BOUNDARY Push到自动释放池的栈顶,并且返回这个POOL_BOUNDARY(哨兵对象)
而当方法objc_autoreleasePoolPop被调用的时候,就会像自动释放池中的对象发送release消息,直到第一个POOL_BOUNDARY


objc_autoreleasePoolPush

通过上面的分析,我们已经直到objc_autoreleasePoolPush实际调用的就是push方法,那么我们就进入push方法里面去一探究竟。

static inline void *push() 
    {
        id *dest;
        ///判断是否有pool
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            // 压栈一个POOL_BOUNDARY,哨兵压栈
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
  • 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);
        }
    }

上面分成了三种不同的情况:

  • 1:有hotPage,并且当前page不满
    • 调用page->add(obj)方法,将对象添加至AutoreleasePoolPage的栈中。
  • 2:有hotPage并且page已满
    • 调用autoreleaseFullOPage初始化一个新的页;
    • 调用page->add(obj)方法,将对象添加至AutoreleasePoolPage的栈中(这个在autoreleaseFullOPage函数里面有)。
  • 3:无hotPage
    • 调用autoreleaseNoPage创建一个hotPage
    • 调用page->add(obj)方法,将对象添加到AutoreleasePoolPage的栈中。

通过上面可以看到,最后都会调用page->add(obj)方法,将对象添加到自动释放池中。hotPage可以理解为当前正在使用的AutoreleasePoolPage

  • page->add(obj)
id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

这个方法其实就是一个压栈的操作,将对象加入AutoreleasePoolPage,饭后移动栈顶指针。

  • autoreleaseFullOPage(当前的hotPage已满)
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;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

它会从传入的page开始遍历整个双向链表,直到
1:查找到一个未满的AutoreleasePoolPage
2:使用构造器传入parent创建一个新的AutoreleasePoolPage
在查找到一个可以使用的AutoreleasePoolPage之后,会将该页面标记成houPage,然后调用page->add(obj)方法,添加对象。

  • autoreleaseNoPage(没有hotPage)
    如果当前内存中不存在hotPage的时候,就会调用autoreleaseNoPage方法初始化一个AutoreleasePoolPage
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()) {
            // 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", 
                         objc_thread_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();
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }

由于是从新构建的自动释放池的双向链表,所以新的AutoreleasePoolPage没有parent指针。
初始化之后,将当前页标记为hotPage,然后先向这个page中添加一个POOL_BOUNDARY(哨兵对象),确保在pop的时候不会报错。
最后依然是page->add(obj),将对象添加到自动释放池。


objc_autoreleasePoolPop
void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

可以看到objc_autoreleasePoolPop方法的调用,是有一个参数传递进来的。那么这个参数是什么呢?不知道大家还记不记得我们上面clang出来的main.cpp文件。我们再来看一下里面的代码:

struct __AtAutoreleasePool {
    ///构造函数
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
    ///析构函数
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

大家发现没有,传入的参数就是push压栈后返回的哨兵对象atautoreleasepoolobj
通过上面我们已经知道,objc_autoreleasePoolPop最终调用的是pop方法,那么我们就来看一下pop方法:

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        // 判断token是否是空占位符
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            // 获取当前页
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                // 如果当前页不存在,则清楚空占位符
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            // 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置
            page = coldPage();
            token = page->begin();
        } else {
            // 获取token所在的页
            page = pageForPointer(token);
        }

        stop = (id *)token;
        // 判断最后一个位置,是否是哨兵
        if (*stop != POOL_BOUNDARY) {
            // 进入 if 说明最后一个位置不是哨兵,也就是说最后一个位置是一个对象
            
            
            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
            } else {
                // 出现混乱
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        // 出栈页
        return popPage<false>(token, page, stop);
    }
  • 我们在pop方法中看到,正常情况下,最后出栈调用的是popPage方法;那么我们再来追踪popPage方法。
template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        // 出栈当前操作页面对象
        page->releaseUntil(stop);

        // memory: delete empty children
        // 删除空子项
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            // 特殊情况:debug期间,删除所有的池
            
            // 获取当前页的父结点
            AutoreleasePoolPage *parent = page->parent;
            // 将当前页kill
            page->kill();
            // 设置操作页为父结点页
            setHotPage(parent);
        } else if (allowDebug && 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) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

可以看到popPage中,会通过releaseUntil出栈当前页stop位置之前的所有对象,即向栈中的对象发送release消息,直到遇到传入的哨兵对象。还有就是killchild页,这一步操作可能有什么其他的考虑,暂时不是很清楚。但是既然是出栈,那重点就是releaseUntil;延续我们之前的思路,继续追踪releaseUntil

    // 释放 stop 位置之前的所有对象
    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        // 判断下一个对象是否是stop,如果不是继续循环
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            // 获取当前操作页,即hot页
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            // 如果当前页为空
            while (page->empty()) {
                // 将page的父结点页赋值个page
                page = page->parent;
                // 设置当前页为父结点页
                setHotPage(page);
            }

            page->unprotect();
            // page->next减减,出栈
            id obj = *--page->next;
            // 将page->next位置的索引,设置为SCRIBBLE,表示已经被释放
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                // 对象不是哨兵,就释放对象
                objc_release(obj);
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }

通过源码我们开看到,raleaseUntil主要是通过循环遍历,判断当前对象是否是stop,其目的是释放stop之前的所有对象
i:首先通过pagenext获得对象,对next进行减减操作,并且对索引进行更改;
ii:判断获得的对象是否为哨兵对象,如果不是,就释放对象。

  • 在上面我们还提到了kill,同过字面意思也能理解这个方法是做什么的。
    它会将当前页面以及子页面全部销毁。
    不过我们还是再来看一期其内部实现:
    void kill() 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        AutoreleasePoolPage *page = this;
        // 一直循环到最后一页
        while (page->child) page = page->child;

        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->unprotect();
                page->child = nil;
                page->protect();
            }
            delete deathptr;
        } while (deathptr != this);
    }

探索了这么多,我们对于自动释放池本质压栈出栈都有了一定的了解。但是还有一个知识点我们还没探索到,那就是autorelease

autorelease
  • 如果不是对象,或者是小对象,直接返回;
  • 如果是对象,则调用对象的autorelease方法,进行释放。
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

跟进对象的autorelease方法:

// objc_object::autorelease()inline id 
objc_object::autorelease()
{
    // 判断是否是 `Tagged Pointer`,这个函数并不希望处理的对象是`Tagged Pointer`
    ASSERT(!isTaggedPointer());
    // 通过 `hasCustomRR`,检查 类(包括其父类)中是否含有默认的方法
    if (fastpath(!ISA()->hasCustomRR())) {
        // 如果没有,调用`rootAutorelease`函数
        return rootAutorelease();
    }
    // 如果有,则调用自定义方法
    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
👇👇👇
// objc_object::rootAutorelease()
inline id 
objc_object::rootAutorelease()
{
    // 如果是小对象,直接返回
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
👇👇👇
// objc_object::rootAutorelease2()
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
👇👇👇
// AutoreleasePoolPage::autorelease((id)this)
static inline id autorelease(id obj)
    {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        // 压栈操作
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }
👇👇👇
// AutoreleasePoolPage::autoreleaseFast((id)this)
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);
        }
    }

所以autorelease的函数调用栈是这个样子的:


看到autorelease的函数调用栈之后,不知道大家有没有感到熟悉;没错,有很多方法,我们在上面探索objc_autoreleasePoolPush的时候也见到过。那么我们将autorelease和我们的压栈结合起来,把整个流程串起来:

自动释放池出栈流程图:


总结:
1、自动释放池是由AutoreleasePoolPage双向链表的形式实现的。
2、当对象调用autorelease方法的时候,会将对象加入AutoreleasePoolPage的栈中
3、调用AutoreleasePoolPage::pop方法会向栈中的对象发送release消息。也就是我们所说的出栈,主要通过page-next递减操作来完成,当出栈对象不是哨兵的时候,释放对象。


  • Tips
    • assert断言
      我们在源码中发现有大量的断言使用,比如:ASSERT(!hotPage());
      在源码中,我们看到它是一个宏,我们跟进去看看这个宏是什么样的:
// An assert that's disabled for release builds but still ensures the expression compiles.
#ifdef NDEBUG
#define ASSERT(x) (void)sizeof(!(x))
#else
#define ASSERT(x) assert(x)
#endif

*****************
我们看到官方注释的很清楚,这个断言不能用于发布模式。

assert的作用是:校验传入的参数是否为;如果为,则向stderr打印一条出错信息,然后通过abort来终止应用程序。
assert的缺点是:频繁的调用会极大的影响程序的性能,增加额外的开销。

用法和注意事项:
1、在函数开始处,检验传入参数的合法性

int resetBufferSize(int newSize) {
 assert(newSize >= 0);
 assert(newSize <= MaxSize);
}

2、每个assert只校验一个条件,因为同时校验多个条件时,如果断言失败,无法直观的判断是哪个条件失败。

// 错误示范
assert(a > 0 && b > 0);
// 正确示范
assert(a>0);
assert(b>0);

3、不能使用改变环境的语句。

// 错误示范 如果在执行之前 `i==100` , 那么这条语句就不会执行,那么`i++`这条命令就没有执行。
assert(i++ < 100);
// 正确示范
assert(i < 100);
i++;

4、assert和后面的语句应空一行,以形成逻辑个视觉上的一致感。

  • Tagged Pointer
    Tagged Pointer是一个特别的指针,它分为两个部分:
    i:一部分直接保存数据;
    ii:一部分作为特殊标记,表示这个是一个特别的指针,不指向任何一个地方。
    因此Tagged Pointer也被叫做伪指针
    • Tagged Pointer被设计的目的是用来存储较小的对象,例如NSNumberNSDateNSString等等。
    • Tagged Pointer的值不再表示地址,而是真正的值。
    • Tagged Pointer在内存读取上有这3倍的效率,创建的时候比以前快106倍。

常见面试题

  • 面试题1:临时变量什么时候释放?

    • 如果在正常情况下,一般是超出作用域就会立即释放
    • 如果将临时变量加入了自动释放池,会延迟释放,即在RunLoop休眠AutoreleasePool作用域之后释放
  • 面试题2:AutoreleasePool原理

    • 自动释放池的本质是一个AutoreleasePoolPage结构体对象,是一个栈结构存储的页,每一个AutoreleasePoolPage都是以双向链表的形式连接的。
    • 自动释放池的压栈出栈主要是通过结构体的构造函数析构函数调用底层的objc_autoreleasePoolPushobjc_autoreleasePoolPop,进而调用AutoreleasePoolPagepushpop两个方法。
    • 每次调用push操作,其实就是创建一个新的AutoreleasePoolPage,而AutoreleasePoolPage的具体操作就是插入一个POOL_BOUNDARY(哨兵对象),并返回插入POOL_BOUNDARY的内存地址。而push内部调用autoreleaseFast方法处理,主要有以下三种情况:
      • page存在,且不满的时候,调用add方法将对象添加至pagenext指针处,并将next递增。
      • page存在,且已满的时候,调用autoreleaseFullPage初始化一个新的page,然后调用add方法将对象添加至page栈中。
      • page不存在的时候,调用autoreleaseNoPage创建一个hotPage,然后调用add方法,将对象添加到page栈中。
  • 当执行pop操作的时候,会传入一个值,这个值就是push操作的返回值,即POOL_BOUNDARY的内存地址token。所以pop内部的实现就是根据token找到哨兵对象所处的page(页),然后使用objc_release释放token之前的所有对象,并把next指针指向正确的位置。

  • 面试题3:AutoreleasePool能否嵌套使用?

    • 可以嵌套使用,其目的是控制应用程序的内存峰值,使其不要太高。
    • 可以嵌套使用的原因是因为:自动释放池是以为结点,通过双向链表的形式连接的,且是和线程一一对应的。
    • 自动释放池的多层嵌套其实就是不停的push哨兵对象,在pop时,会先释放里面的,再释放外面的。
  • 面试题4:哪些对象可以加入AutoreleasePool?alloc创建的可以吗?

    • 使用new / alloc / copy关键字生成的对象 和 retain了的对象需要手动释放,不会被添加到自动释放池中。
    • 设置为autorelease的对象不需要手动释放,会直接进入自动释放池。
    • 所有autorelease的对象,在出了作用域之后,会被自动添加到最新创建的自动释放池之中。
  • 面试题5:AutoreleasePool的释放时机是什么时候?

    • APP启动之后,系统在主线程RunLoop里面注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHander()
    • 第一个Observer监听的事件是Entry(即将进入RunLoop),其回调内会调用_objc_autoreleasePoolPush()创建自动释放池。其order-2147183647(优先级最高),保证创建自动释放池发生在其他所有回调之前。
    • 第二个Observer监听两个事件:
      1、BeforWaiting(准备进入休眠),这个时候调用_objc_autoreleasePoolPop() & _objc_autoreleasePoolPush(),释放旧池并创建新池
      2、Exit(即将推出Loop),这个时候调用_objc_autoreleasePoolPop()释放自动释放池。这个Observerorder2147483647(优先级最低),保证其释放池子的操作发生在其他所有回调之后。
  • 面试题6:thread 和 AutoreleasePool的关系

    • 每个线程(包括主线程在内),都维护了自己的自动释放池堆栈结构
    • 新的自动释放池在被创建的时候,会被添加到栈顶;当自动释放池销毁的时候,会从中移除。
    • 对于当前线程来说,会将自动释放池对象放入自动释放池的栈顶;在线程停止的时候,会自动释放掉与该线程关联的所有自动释放池

每个线程都有与之关联的自动释放池堆栈结构,新的pool在创建的时候回被压栈到栈顶;pool销毁的时候,会被出栈。
对于当前线程来说,释放对象呗压栈到栈顶,线程停止时,会自动释放与之关联的自动释放池。

  • 面试题7:RunLoop 和 AutoreleasePool的关系
    • 主程序的RunLoop在每次事件循环之前,会自动创建一个autoreleasepool
    • 事件循环结束的时候,执行drain操作,释放其中的对象。

参考资料
AutoReleasePool & NSRunLoop 底层分析
自动释放池的前世今生 ---- 深入解析 autoreleasepool
断言(assert)的用法
聊聊伪指针 Tagged Pointer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容