iOS 內(nèi)存管理(二)AutoReleasePool

AutoReleasePool 自動(dòng)釋放池

AutoReleasePool是OC的內(nèi)存自動(dòng)回收機(jī)制,將加入到AutoReleasePool中的變量release時(shí)機(jī)延遲。將對(duì)象加入到AutoReleasePool中,這個(gè)對(duì)象即使超出作用域也不會(huì)立即釋放,直到runloop休眠或者超出AutoReleasePool作用域才會(huì)釋放

AutoReleasePool

  • 1、程序啟動(dòng)到加載完成,主線程對(duì)應(yīng)的Runloop處于休眠狀態(tài),直到用戶點(diǎn)擊交互喚醒Runloop
  • 2、用戶每次交互都會(huì)啟動(dòng)一次Runloop用來(lái)處理用戶的點(diǎn)擊、交互事件
  • 3、Runloop被喚醒后,會(huì)自動(dòng)創(chuàng)建AutoReleasePool,并將所有延遲釋放的對(duì)象添加到AutoReleasePool
  • 4、在一次完整的Runloop執(zhí)行結(jié)束前,會(huì)自動(dòng)向AutoReleasePool中的對(duì)象發(fā)送release消息,然后銷毀AutoReleasePool

Clang 分析

  • 在main.m中定義下面代碼,通過(guò)xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m命令將其編譯成main.cpp
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
}
  • 從.cpp文件中可以看出自動(dòng)釋放池的本質(zhì)是一個(gè)結(jié)構(gòu)體對(duì)象,調(diào)用objc_autoreleasePoolPushobjc_autoreleasePoolPop兩個(gè)方法
struct __AtAutoreleasePool {
    //構(gòu)造函數(shù)
    __AtAutoreleasePool() {
            atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    //析構(gòu)函數(shù)
    ~__AtAutoreleasePool() {
            objc_autoreleasePoolPop(atautoreleasepoolobj);
     }
      void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
   { 
        //是一個(gè)結(jié)構(gòu)體
         __AtAutoreleasePool __autoreleasepool; 
    }
    return 0;
}

AutoreleasePoolPage 底層原理

objc源碼中,有關(guān)于AutoreleasePoolPage的解釋

Autorelease pool implementation

- A thread's autorelease pool is a stack of pointers. 
線程的自動(dòng)釋放池是指針的堆棧

- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每個(gè)指針都是要釋放的對(duì)象,或者是POOL_BOUNDARY,它是自動(dòng)釋放池的邊界。

- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
池令牌是指向該池的POOL_BOUNDARY的指針。彈出池后,將釋放比哨點(diǎn)更熱的每個(gè)對(duì)象。

- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. 
堆棧分為兩個(gè)雙向鏈接的頁(yè)面列表。根據(jù)需要添加和刪除頁(yè)面。

- Thread-local storage points to the hot page, where newly autoreleased objects are stored. 
線程本地存儲(chǔ)指向熱頁(yè)面,該頁(yè)面存儲(chǔ)新自動(dòng)釋放的對(duì)象。
  • objc_autoreleasePoolPushobjc_autoreleasePoolPop方法源碼
//***********push方法***********
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

//***********pop方法***********
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
  • 從上面可以發(fā)現(xiàn),都是調(diào)用AutoreleasePoolPagepushpop方法,從AutoreleasePoolPage定義中可以發(fā)現(xiàn)自動(dòng)釋放池是一個(gè)頁(yè),也是一個(gè)對(duì)象,大小是4096
//************宏定義************
#define PAGE_MIN_SIZE           PAGE_SIZE
#define PAGE_SIZE               I386_PGBYTES
#define I386_PGBYTES            4096            /* bytes per 80386 page */

//************類定義************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    //頁(yè)的大小
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif

private:
    
    ...
    
    //構(gòu)造函數(shù)
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),//開(kāi)始存儲(chǔ)的位置
                                objc_thread_self(),//傳的是當(dāng)前線程,當(dāng)前線程時(shí)通過(guò)tls獲取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一頁(yè)深度為0,往后是前一個(gè)的深度+1
                                newParent ? newParent->hiwat : 0)
    {...}
    
    //析構(gòu)函數(shù)
    ~AutoreleasePoolPage() {...}
    
    ...
    
    //頁(yè)的開(kāi)始位置
    id * begin() {...}
    
    //頁(yè)的結(jié)束位置
    id * end() {...}
   
    //頁(yè)是否為空
    bool empty() {...}
    
    //頁(yè)是否滿了
    bool full() {...}
   
    //頁(yè)的存儲(chǔ)是否少于一半
    bool lessThanHalfFull() {...}
     
     //添加釋放對(duì)象
    id *add(id obj){...}
    
    //釋放所有對(duì)象
    void releaseAll() {...}
    
    //釋放到stop位置之前的所有對(duì)象
    void releaseUntil(id *stop) {...}
    
    //殺掉
    void kill() {...}
    
    //釋放本地線程存儲(chǔ)空間
    static void tls_dealloc(void *p) {...}
    
    //獲取AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) {...}
    static AutoreleasePoolPage *pageForPointer(uintptr_t p)  {...}
    
    //是否有空池占位符
    static inline bool haveEmptyPoolPlaceholder() {...}
    
    //設(shè)置空池占位符
    static inline id* setEmptyPoolPlaceholder(){...}
    
    //獲取當(dāng)前操作頁(yè)
    static inline AutoreleasePoolPage *hotPage(){...}
    
    //設(shè)置當(dāng)前操作頁(yè)
    static inline void setHotPage(AutoreleasePoolPage *page) {...}
    
    //獲取coldPage
    static inline AutoreleasePoolPage *coldPage() {...}
    
    //快速釋放
    static inline id *autoreleaseFast(id obj){...}
   
   //添加自動(dòng)釋放對(duì)象,當(dāng)頁(yè)滿的時(shí)候調(diào)用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}
    
    //添加自動(dòng)釋放對(duì)象,當(dāng)沒(méi)頁(yè)的時(shí)候使用這個(gè)方法
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj){...}
   
   //創(chuàng)建新頁(yè)
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj) {...}
    
public:
    //自動(dòng)釋放
    static inline id autorelease(id obj){...}
   
    //入棧
    static inline void *push() {...}
    
    //兼容老的 SDK 出棧方法
    __attribute__((noinline, cold))
    static void badPop(void *token){...}
    
    //出棧頁(yè)面
    template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop){...}
    __attribute__((noinline, cold))
    static void
    popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){...}
    
    //出棧
    static inline void
    pop(void *token){...}
    
    static void init(){...}
    
    //打印
    __attribute__((noinline, cold))
    void print(){...}
    
    //打印所有
    __attribute__((noinline, cold))
    static void printAll(){...}
    
    //打印Hiwat
    __attribute__((noinline, cold))
    static void printHiwat(){...}
  • 從定義中發(fā)現(xiàn)AutoreleasePoolPage繼承自AutoreleasePoolPageData
    • AutoreleasePoolPageData的定義中發(fā)現(xiàn)了parentchild,這代表AutoreleasePoolPageData是一個(gè)雙向鏈表,其內(nèi)存大小為56字節(jié)
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    //用來(lái)校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
    magic_t const magic;//16個(gè)字節(jié)
    //指向最新添加的autoreleased對(duì)象的下一個(gè)位置,初始化時(shí)指向begin()
    __unsafe_unretained id *next;//8字節(jié)
    //指向當(dāng)前線程
    pthread_t const thread;//8字節(jié)
    //指向父節(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點(diǎn),最后一個(gè)結(jié)點(diǎn)的child值為nil
    AutoreleasePoolPage *child;//8字節(jié)
    //表示深度,從0開(kāi)始,往后遞增1
    uint32_t const depth;//4字節(jié)
    //表示high water mark 最大入棧數(shù)量標(biāo)記
    uint32_t hiwat;//4字節(jié)

    //初始化
    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)
    {
    }
};

objc_autoreleasePoolPush源碼

進(jìn)入push源碼

  • 判斷是否有pool
    • 沒(méi)有,autoreleaseNewPage創(chuàng)建
    • 有,autoreleaseFast方法壓棧一個(gè)哨兵對(duì)象
//入棧
static inline void *push() 
{
    id *dest;
    //判斷是否有pool
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.自動(dòng)釋放池從新池頁(yè)面開(kāi)始
        //如果沒(méi)有,則創(chuàng)建
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        //壓棧一個(gè)POOL_BOUNDARY,即壓棧哨兵
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
autoreleaseNewPage -- 創(chuàng)建頁(yè)

搜索objc_autoreleasePoolPush -> push -> autoreleaseNewPage進(jìn)入源碼實(shí)現(xiàn),通過(guò)判斷獲取hotPage,判斷當(dāng)前頁(yè)是否存在

  • 存在,autoreleaseFullPage壓棧對(duì)象
  • 不存在,autoreleaseNoPage創(chuàng)建新頁(yè)
//創(chuàng)建新頁(yè)
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    //獲取當(dāng)前操作頁(yè)
    AutoreleasePoolPage *page = hotPage();
    //如果存在,則壓棧對(duì)象
    if (page) return autoreleaseFullPage(obj, page);
    //如果不存在,則創(chuàng)建頁(yè)
    else return autoreleaseNoPage(obj);
}

//******** hotPage方法 ********
//獲取當(dāng)前操作頁(yè)
static inline AutoreleasePoolPage *hotPage() 
{
    //獲取當(dāng)前頁(yè)
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    //如果是一個(gè)空池,則返回nil,否則,返回當(dāng)前線程的自動(dòng)釋放池
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    if (result) result->fastcheck();
    return result;
}


//******** autoreleaseNoPage方法 ********
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;
    //判斷是否是空占位符,如果是,則壓棧哨兵標(biāo)識(shí)符置為YES
    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;
    }
    //如果對(duì)象不是哨兵對(duì)象,且沒(méi)有Pool,則報(bào)錯(cuò)
    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;
    }
    //如果對(duì)象是哨兵對(duì)象,且沒(méi)有申請(qǐng)自動(dòng)釋放池內(nèi)存,則設(shè)置一個(gè)空占位符存儲(chǔ)在tls中,其目的是為了節(jié)省內(nèi)存
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {//如果傳入?yún)?shù)為哨兵
        // 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();//設(shè)置空的占位符
    }

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

    // Install the first page.
    //初始化第一頁(yè)
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    //設(shè)置page為當(dāng)前聚焦頁(yè)
    setHotPage(page);
    
    // Push a boundary on behalf of the previously-placeholder'd pool.
    //壓棧哨兵的標(biāo)識(shí)符為YES,則壓棧哨兵對(duì)象
    if (pushExtraBoundary) {
        //壓棧哨兵
        page->add(POOL_BOUNDARY);
    }
    
    // Push the requested object or pool.
    //壓棧對(duì)象
    return page->add(obj);
}
  • 通過(guò)AutoreleasePoolPage方法創(chuàng)建當(dāng)前線程的自動(dòng)釋放池,其中的構(gòu)造方法通過(guò)父類AutoreleasePoolPageData初始化完成
  • begin():表示壓棧的位置(下一個(gè)要釋放對(duì)象的壓棧地址)
//********begin()********
//頁(yè)的開(kāi)始位置
id * begin() {
    //等于 首地址+56(AutoreleasePoolPage類所占內(nèi)存大?。?    return (id *) ((uint8_t *)this+sizeof(*this));
}
  • objc_thread_self():通過(guò)tls獲取當(dāng)前線程
__attribute__((const))
static inline pthread_t objc_thread_self()
{
    //通過(guò)tls獲取當(dāng)前線程
    return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
  • newParent:表示父節(jié)點(diǎn)
  • 最后兩個(gè)參數(shù)是通過(guò)父節(jié)點(diǎn)的深度,最大入棧個(gè)數(shù)計(jì)算depth和hiwat
//**********AutoreleasePoolPage構(gòu)造方法**********
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),//開(kāi)始存儲(chǔ)的位置
                                objc_thread_self(),//傳的是當(dāng)前線程,當(dāng)前線程時(shí)通過(guò)tls獲取的
                                newParent,
                                newParent ? 1+newParent->depth : 0,//如果是第一頁(yè)深度為0,往后是前一個(gè)的深度+1
                                newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        //this 表示 新建頁(yè)面,將當(dāng)前頁(yè)面的子節(jié)點(diǎn) 賦值為新建頁(yè)面
        parent->child = this;
        parent->protect();
    }
    protect();
}

//**********AutoreleasePoolPageData初始化方法**********
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)
    {
    }
自動(dòng)釋放池內(nèi)存結(jié)構(gòu)
  • 切換到MRC環(huán)境下,Build Settings -> Objectice-C Automatic Reference Counting設(shè)置為NO

    image.png

  • 定義下面代碼,并運(yùn)行

//************打印自動(dòng)釋放池結(jié)構(gòu)************
extern void _objc_autoreleasePoolPrint(void);

//************運(yùn)行代碼************
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //循環(huán)創(chuàng)建對(duì)象,并加入自動(dòng)釋放池
        for (int i = 0; i < 5; i++) {
             NSObject *objc = [[NSObject alloc] sutorelease];
        }
        //調(diào)用
        _objc_autoreleasePoolPrint();
    }
}

發(fā)現(xiàn)打印了6個(gè)對(duì)象,其中POOL表示哨兵,即邊界,防止越界,頁(yè)的首地址和POOL相差0x38(即56),也就是AutoreleasePoolPage自身大小

image.png

將測(cè)試代碼數(shù)據(jù)5改為505,發(fā)現(xiàn)第一頁(yè)只存儲(chǔ)了504個(gè)對(duì)象,第二頁(yè)存儲(chǔ)了1個(gè)


image.png

將測(cè)試代碼數(shù)據(jù)5改為505+506,發(fā)現(xiàn)第一頁(yè)只存儲(chǔ)了504個(gè)對(duì)象,第二頁(yè)存儲(chǔ)了505個(gè),第三頁(yè)存儲(chǔ)了2個(gè)


image.png

autoreleaseFast 壓棧對(duì)象

進(jìn)入autoreleaseFast源碼

  • 獲取當(dāng)前操作頁(yè)
    • 存在且未滿,add方法壓棧
    • 存在且滿了,autoreleaseFullPage進(jìn)入下一頁(yè)
    • 不存在,autoreleaseNoPage創(chuàng)建新頁(yè)
static inline id *autoreleaseFast(id obj)
{
    //獲取當(dāng)前操作頁(yè)
    AutoreleasePoolPage *page = hotPage();
    //判斷頁(yè)是否滿了
    if (page && !page->full()) {
        //如果未滿,則壓棧
        return page->add(obj);
    } else if (page) {
        //如果滿了,則安排新的頁(yè)面
        return autoreleaseFullPage(obj, page);
    } else {
        //頁(yè)不存在,則新建頁(yè)
        return autoreleaseNoPage(obj);
    }
}
autoreleaseFullPage

判斷當(dāng)前頁(yè)是否存儲(chǔ)滿了,如果滿了通過(guò)do-while循環(huán)查找子節(jié)點(diǎn)對(duì)應(yīng)的頁(yè),如果沒(méi)有子節(jié)點(diǎn)就創(chuàng)建新的頁(yè)并壓棧

//添加自動(dòng)釋放對(duì)象,當(dāng)頁(yè)滿的時(shí)候調(diào)用這個(gè)方法
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-while遍歷循環(huán)查找界面是否滿了
    do {
        //如果子頁(yè)面存在,則將頁(yè)面替換為子頁(yè)面
        if (page->child) page = page->child;
        //如果子頁(yè)面不存在,則新建頁(yè)面
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    //設(shè)置為當(dāng)前操作頁(yè)面
    setHotPage(page);
    //對(duì)象壓棧
    return page->add(obj);
}
add方法

添加釋放對(duì)象,通過(guò)next指針存儲(chǔ)釋放的對(duì)象,并next指針遞增,表示下一個(gè)釋放對(duì)象的存儲(chǔ)位置

//添加釋放對(duì)象
id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    //傳入對(duì)象存儲(chǔ)的位置
    id *ret = next;  // faster than `return next-1` because of aliasing
    //將obj壓棧到next指針位置,然后next進(jìn)行++,即下一個(gè)對(duì)象存儲(chǔ)的位置
    *next++ = obj;
    protect();
    return ret;
}

autorelease底層

查看autorelease源碼

  • 如果不是對(duì)象,直接返回
  • 如果是小對(duì)象,直接返回
  • 調(diào)用autorelease釋放
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    //如果不是對(duì)象,則直接返回
    if (!obj) return obj;
    //如果是小對(duì)象,也直接返回
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

進(jìn)入autorelease源碼,無(wú)論是壓棧哨兵對(duì)象還是普通對(duì)象都會(huì)進(jìn)入該方法,只是區(qū)別標(biāo)志不同

??
inline id 
objc_object::autorelease()
{
    ASSERT(!isTaggedPointer());
    //判斷是否是自定義類
    if (fastpath(!ISA()->hasCustomRR())) {
        return rootAutorelease();
    }

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
??
inline id 
objc_object::rootAutorelease()
{
    //如果是小對(duì)象,直接返回
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
??
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
??
static inline id autorelease(id obj)
{
    ASSERT(obj);
    ASSERT(!obj->isTaggedPointer());
    //autoreleaseFast 壓棧操作
    id *dest __unused = autoreleaseFast(obj);
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}

objc_autoreleasePoolPop 源碼分析

objc_autoreleasePoolPop方法中傳入一個(gè)對(duì)象---push壓棧后的哨兵對(duì)象,即ctxt防止出?;靵y

  • 進(jìn)入pop源碼
    • 空頁(yè)處理
    • 根據(jù)token獲取page
    • 容錯(cuò)處理
    • popPage出棧
//出棧
static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
   //判斷對(duì)象是否是空占位符
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        //如果當(dāng)是空占位符
        // Popping the top-level placeholder pool.
        //獲取當(dāng)前頁(yè)
        page = hotPage();
        if (!page) {
            // Pool was never used. Clear the placeholder.
            //如果當(dāng)前頁(yè)不存在,則清除空占位符
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        //如果當(dāng)前頁(yè)存在,則將當(dāng)前頁(yè)設(shè)置為coldPage,token設(shè)置為coldPage的開(kāi)始位置
        page = coldPage();
        token = page->begin();
    } else {
        //獲取token所在的頁(yè)
        page = pageForPointer(token);
    }
    
    stop = (id *)token;
    //判斷最后一個(gè)位置,是否是哨兵
    if (*stop != POOL_BOUNDARY) {
        //最后一個(gè)位置不是哨兵,即最后一個(gè)位置是一個(gè)對(duì)象
        if (stop == page->begin()  &&  !page->parent) {
            //如果是第一個(gè)位置,且沒(méi)有父節(jié)點(diǎn),什么也不做
            // 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 {
            //如果是第一個(gè)位置,且有父節(jié)點(diǎn),則出現(xiàn)了混亂
            // 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);
    }
    //出棧頁(yè)
    return popPage<false>(token, page, stop);
}

  • 進(jìn)入popPage源碼,其中傳入的allowDebug為false,通過(guò)releaseUntil出棧,即向棧中對(duì)象發(fā)送release消息,知道遇到哨兵對(duì)象
//出棧頁(yè)面
template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    if (allowDebug && PrintPoolHiwat) printHiwat();
    //出棧當(dāng)前操作頁(yè)面對(duì)象
    page->releaseUntil(stop);

    // memory: delete empty children 刪除空子項(xiàng)
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        //調(diào)試期間刪除每個(gè)特殊情況下的所有池
        //獲取當(dāng)前頁(yè)面的父節(jié)點(diǎn)
        AutoreleasePoolPage *parent = page->parent;
        //將當(dāng)前頁(yè)面殺掉
        page->kill();
        //設(shè)置操作頁(yè)面為父節(jié)點(diǎn)頁(yè)面
        setHotPage(parent);
    }
    else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top)
        // when debugging missing autorelease pools
        //特殊情況:調(diào)試丟失的自動(dòng)釋放池時(shí)刪除pop(top)的所有內(nèi)容
        page->kill();
        setHotPage(nil);
    }
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full 如果頁(yè)面已滿一半以上,則保留一個(gè)空子級(jí)
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}
  • 進(jìn)入releaseUntil源碼,通過(guò)while循環(huán),判斷對(duì)象是否stop,釋放哨兵對(duì)象之前的所有對(duì)象
//釋放到stop位置之前的所有對(duì)象
void releaseUntil(id *stop) 
{
    // Not recursive: we don't want to blow out the stack  不是遞歸的:我們不想破壞堆棧
    // if a thread accumulates a stupendous amount of garbage
    //判斷下一個(gè)對(duì)象是否等于stop,如果不等于,則進(jìn)入while循環(huán)
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release 
        // autoreleased more objects 每次從hotPage()重新啟動(dòng),以防-release自動(dòng)釋放更多對(duì)象
        //獲取當(dāng)前操作頁(yè)面,即hot頁(yè)面
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        //如果當(dāng)前頁(yè)是空的
        while (page->empty()) {
            //將page賦值為父節(jié)點(diǎn)頁(yè)
            page = page->parent;
            //并設(shè)置當(dāng)前頁(yè)為父節(jié)點(diǎn)頁(yè)
            setHotPage(page);
        }

        page->unprotect();
        //next進(jìn)行--操作,即出棧
        id obj = *--page->next;
        //將頁(yè)索引位置置為SCRIBBLE,表示已經(jīng)被釋放
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
            //釋放
            objc_release(obj);
        }
    }
    //設(shè)置當(dāng)前頁(yè)
    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        ASSERT(page->empty());
    }
#endif
}
  • 進(jìn)入kell實(shí)現(xiàn),銷毀當(dāng)前頁(yè),將當(dāng)前頁(yè)賦值為父節(jié)點(diǎn)頁(yè),并將父節(jié)點(diǎn)頁(yè)的child對(duì)象指針置為nil
//銷毀
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;
    //獲取最后一個(gè)頁(yè)
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        //子節(jié)點(diǎn) 變成 父節(jié)點(diǎn)
        page = page->parent;
        if (page) {
            page->unprotect();
            //子節(jié)點(diǎn)為nil
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

總結(jié)

自動(dòng)釋放池 -- 內(nèi)存結(jié)構(gòu)
  • 只有第一頁(yè)有哨兵對(duì)象
  • 當(dāng)一頁(yè)壓棧滿了會(huì)開(kāi)辟新頁(yè),第一頁(yè)最多存儲(chǔ)504個(gè)對(duì)象,其余最多存儲(chǔ)505個(gè)
  • 一頁(yè)大小等于505*8=4040


    AutoreleasePool內(nèi)存結(jié)構(gòu)圖
自動(dòng)釋放池 -- push壓棧
  • 當(dāng)沒(méi)有pool,創(chuàng)建新頁(yè),壓棧哨兵對(duì)象
  • 在頁(yè)中通過(guò)next指針遞增壓棧對(duì)象
  • 當(dāng)頁(yè)滿了,創(chuàng)建新頁(yè)并設(shè)置當(dāng)前頁(yè)的child對(duì)象為新頁(yè)
push壓棧
自動(dòng)釋放池 -- pop出棧
  • 通過(guò)next指針遞減出棧普通對(duì)象
  • 當(dāng)前頁(yè)空了,設(shè)置父節(jié)點(diǎn)頁(yè)為hot頁(yè),kill當(dāng)前頁(yè)
pop出棧

面試題

1、臨時(shí)變量什么時(shí)候釋放

  • 正常情況下,超出作用域立即釋放
  • 加入自動(dòng)釋放池,會(huì)延遲釋放,在Runloop休眠或者autoreleasepool 作用域之后釋放

2、AutoreleasePool原理

  • 自動(dòng)釋放池本質(zhì)是一個(gè)AutoreleasePoolPage結(jié)構(gòu)體對(duì)象,棧結(jié)構(gòu)存儲(chǔ),每一個(gè)AutoreleasePoolPage以雙向鏈表形式連接
  • 自動(dòng)釋放的壓棧和出棧本質(zhì)上是調(diào)用AutoreleasePoolPagepushpop方法
  • push 壓棧
    • 判斷hotPage是否存在
      • 不存在,autoreleaseNoPage創(chuàng)建新hotPage,調(diào)用add方法將對(duì)象添加至page棧中
      • 存在滿了,autoreleaseFullPage初始新的page
      • 存在沒(méi)滿,調(diào)用add方法將對(duì)象添加到page的next指針,next指針++
  • pop 出棧
    執(zhí)行pop出棧時(shí),會(huì)傳入push操作的返回值,即POOL_BOUNDARY的內(nèi)存地址token,根據(jù)token找到哨兵對(duì)象所在,并釋放之前的對(duì)象,next指針--

3、AutoreleasePool能否嵌套使用?

  • 可以嵌套使用,控制App內(nèi)存峰值
  • 自動(dòng)釋放池是以棧為節(jié)點(diǎn),雙向鏈表的形式,與線程一一對(duì)應(yīng)

4、哪些對(duì)象可以加入AutoreleasePool?alloc創(chuàng)建可以嗎?

  • 使用new、alloc、copy關(guān)鍵字生成的對(duì)象和retain了的對(duì)象需要手動(dòng)釋放,不會(huì)被加入自動(dòng)釋放池
  • 設(shè)置了 autorelease的對(duì)象會(huì)直接進(jìn)入自動(dòng)釋放池
  • 所有autorelease對(duì)象出了作用域后,會(huì)被加入最近的自動(dòng)釋放池

5、main函數(shù)中自動(dòng)釋放池什么時(shí)候銷毀

  • App在啟動(dòng)時(shí),會(huì)在主Runloop中注冊(cè)兩個(gè)Observer,回調(diào)_wrapRunLoopWithAutoreleasePoolHandler ()
  • 第一個(gè)Observer監(jiān)聽(tīng)Entry(即將進(jìn)入Loop),在其回調(diào)中調(diào)用_objc_autoreleasePoolPush ()創(chuàng)建自動(dòng)釋放池,優(yōu)先級(jí)最高,保證創(chuàng)建的自動(dòng)釋放池在所有回調(diào)前
  • 第二個(gè)Observer監(jiān)聽(tīng)兩個(gè)事件
    • BeforeWaiting(準(zhǔn)備進(jìn)入休眠)時(shí)調(diào)用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush ()方法,釋放舊池,創(chuàng)建新池
    • Exit(退出Loop)時(shí)調(diào)用_objc_autoreleasePoolPop ()釋放,優(yōu)先級(jí)最低,保證所有回調(diào)已經(jīng)完成

6、 線程 和 自動(dòng)釋放池 的關(guān)系

官方文檔

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.
  • 每個(gè)線程(包括主線程)都在內(nèi)部維護(hù)了自己的自動(dòng)釋放出堆棧結(jié)構(gòu)
  • 新的自動(dòng)釋放池創(chuàng)建時(shí),會(huì)被添加到棧頂,當(dāng)自動(dòng)釋放池銷毀時(shí),從棧中移除
  • 對(duì)應(yīng)當(dāng)前線程來(lái)說(shuō),會(huì)將自動(dòng)釋放的對(duì)象加入自動(dòng)釋放池,當(dāng)線程停止時(shí),會(huì)自動(dòng)釋放與該線程相關(guān)的所有自動(dòng)釋放池

7、RunLoop 和 AutoreleasePool的關(guān)系

官方文檔

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
  • Runloop在沒(méi)錯(cuò)事件循環(huán)之前,都會(huì)自動(dòng)創(chuàng)建一會(huì)AutoreleasePool
  • 在事件結(jié)束時(shí),執(zhí)行drain,釋放其中的對(duì)象
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容