ios自動(dòng)釋放池

一. 自動(dòng)釋放池源碼解析
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSObject *obj = [[NSObject alloc] init];

    }
    return 0;
}

在main函數(shù)中使用如下命令重寫(xiě)成c++文件

clang -rewrite-objc main.m

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

        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

    }
    return 0;
}

通過(guò)上面的c++代碼可以看到:@autoreleasepool變成了__AtAutoreleasePool __autoreleasepool蛆橡。也就是說(shuō) @autoreleasepool {} 被轉(zhuǎn)換為一個(gè) __AtAutoreleasePool 結(jié)構(gòu)體凫佛。

__AtAutoreleasePool結(jié)構(gòu)
struct __AtAutoreleasePool {
    //構(gòu)造函數(shù)
  // 內(nèi)部調(diào)用objc_autoreleasePoolPush函數(shù)妹孙。創(chuàng)建一個(gè)atautoreleasepoolobj
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
      
  }
// 析構(gòu)函數(shù)
// 內(nèi)部調(diào)用objc_autoreleasePoolPop函數(shù)
  ~__AtAutoreleasePool()
    {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
  void * atautoreleasepoolobj;
};

__AtAutoreleasePool是一個(gè)結(jié)構(gòu)體吏祸,內(nèi)部實(shí)現(xiàn)了一個(gè)構(gòu)造函數(shù)objc_autoreleasePoolPush()創(chuàng)建了一個(gè)自動(dòng)釋放池對(duì)象atautoreleasepoolobj和一個(gè)析構(gòu)函數(shù)objc_autoreleasePoolPop(atautoreleasepoolobj).

由此說(shuō)明自動(dòng)釋放池的使用就是將作用域中的代碼包含在__AtAutoreleasePool的構(gòu)造函數(shù)和析構(gòu)函數(shù)中,由atautoreleasepoolobj對(duì)象對(duì)作用域的內(nèi)存進(jìn)行管理.

這個(gè)結(jié)構(gòu)體會(huì)在初始化時(shí)調(diào)用 objc_autoreleasePoolPush() 方法细睡,會(huì)在析構(gòu)時(shí)調(diào)用 objc_autoreleasePoolPop 方法。
這表明,我們的 main 函數(shù)在實(shí)際工作時(shí)其實(shí)是這樣的:

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();

        // 加入自動(dòng)釋放池中的對(duì)象

        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

二.objc_autoreleasePoolPush的實(shí)現(xiàn)

在runtime源碼中可以看到該函數(shù)的實(shí)現(xiàn)如下:

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

在objc_autoreleasePoolPush函數(shù)內(nèi)部調(diào)用了AutoreleasePoolPage對(duì)象的push函數(shù)。
我們先來(lái)看下AutoreleasePoolPage 的結(jié)構(gòu)

AutoreleasePoolPage結(jié)構(gòu)

AutoreleasePoolPage集成自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;
    
    // 構(gòu)造函數(shù)
    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:用來(lái)校驗(yàn)AutoreleasePoolPage結(jié)構(gòu)是否完整村生;
  • next:指向最新添加的autoreleased對(duì)象的下一個(gè)位置,初始化時(shí)指向begin;
  • thread:指向當(dāng)前線程;
  • parent:指向父結(jié)點(diǎn),第一個(gè)AutoreleasePoolPage結(jié)點(diǎn)的父結(jié)點(diǎn)為nil;
  • child:指向子結(jié)點(diǎn)饼丘,最后一個(gè)AutoreleasePoolPage結(jié)點(diǎn)的子結(jié)點(diǎn)為nil;
  • depth:當(dāng)前結(jié)點(diǎn)的深度趁桃,從0開(kāi)始,往后遞增;
  • hiwat:代表hige water mark最大入棧數(shù)量標(biāo)記;
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_data_t;

public:
    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
}

#define PROTECT_AUTORELEASEPOOL 0 // 說(shuō)明目前版本 SIZE = PAGE_MIN_SIZE;

#define I386_PGBYTES            4096            /* bytes per 80386 page */
#define PAGE_SIZE               I386_PGBYTES
#define PAGE_MIN_SIZE           PAGE_SIZE
// 可以看到PAGE_MIN_SIZE的值是4096

  • 每一個(gè)自動(dòng)釋放池都是由一系列的 AutoreleasePoolPage 組成的肄鸽,并且每一個(gè) AutoreleasePoolPage 的大小都是 4096 字節(jié)(16 進(jìn)制 0x1000)

  • 自動(dòng)釋放池中的 AutoreleasePoolPage 是以雙向鏈表的形式連接起來(lái)的,如下圖


    01.png

如果我們的一個(gè)AutoreleasePoolPage 被初始化在內(nèi)存的 0x100816000 ~ 0x100817000 中卫病,它在內(nèi)存中的結(jié)構(gòu)如下

01.png

其中有 56 字節(jié) 用于存儲(chǔ) AutoreleasePoolPage 的成員變量,剩下的 0x100816038 ~ 0x100817000 都是用來(lái)存儲(chǔ)加入到自動(dòng)釋放池中的對(duì)象典徘。

    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }
  • begin() 和 end() 這兩個(gè)實(shí)例方法幫助我們快速獲取 0x100816038 ~ 0x100817000 這一范圍的邊界地址蟀苛。

  • next 指向了下一個(gè)為空的內(nèi)存地址。

從AutoreleasePoolPage 的結(jié)構(gòu)總可以看到POOL_SENTINEL對(duì)象逮诲。稱之為哨兵對(duì)象帜平。

#define POOL_SENTINEL nil
POOL_SENTINEL 哨兵對(duì)象的作用

每個(gè)自動(dòng)釋放池初始化在調(diào)用objc_autoreleasePoolPush的時(shí)候,都會(huì)把一個(gè)POOL_SENTINEL push到自動(dòng)釋放池的棧頂汛骂,并且返回這個(gè)POOL_SENTINEL的地址罕模。

int main(int argc, const char * argv[]) {
    {
      // atautoreleasepoolobj指向的是哨兵對(duì)象
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        // do whatever you want
        // 傳入哨兵對(duì)象地址
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

上面這個(gè)atautoreleasepoolobj就是一個(gè)POOL_SENTINEL评腺。
可以看到在調(diào)用objc_autoreleasePoolPop時(shí)帘瞭,會(huì)傳進(jìn)去這個(gè)地址:

  • 根據(jù)傳入的哨兵對(duì)象地址找到哨兵對(duì)象所處的page
  • 從最新加入的對(duì)象一直向前清理,可以向前跨越若干個(gè)page蒿讥,直到哨兵所在的page蝶念,在當(dāng)前page中抛腕,將晚于哨兵對(duì)象插入的所有autorelease對(duì)象都發(fā)送一次release消息,并向回移動(dòng)next指針到正確位置媒殉。

回到objc_autoreleasePoolPush的實(shí)現(xiàn)上來(lái)担敌,可以看到objc_autoreleasePoolPush內(nèi)部調(diào)用的是push方法。實(shí)現(xiàn)如下:

    static inline void *push() 
    {
        id *dest;

        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            // 當(dāng)自動(dòng)釋放池出現(xiàn)錯(cuò)誤時(shí)停止廷蓉,并允許堆調(diào)試器跟蹤自動(dòng)釋放池 調(diào)試模式
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

DebugPoolAllocation是調(diào)試模式全封,所以我們只要看autoreleaseFast函數(shù)。
可以看到調(diào)用autoreleaseFast函數(shù)時(shí)傳入了 POOL_BOUNDARY哨兵對(duì)象桃犬。

autoreleaseFast函數(shù)的實(shí)現(xiàn)如下:
    static inline id *autoreleaseFast(id obj)
    {
        //1 獲取hotPage
        AutoreleasePoolPage *page = hotPage();

        if (page && !page->full()) {
            // 2.1 有 hotPage 并且當(dāng)前 page 不滿
            // 調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中
            return page->add(obj);
            
        } else if (page) {
            // 2.2  有 hotPage 并且當(dāng)前 page 已滿
            // 調(diào)用 autoreleaseFullPage 初始化一個(gè)新的頁(yè)
            // 調(diào)用 page->add(obj) 方法將對(duì)象添加至新的 AutoreleasePoolPage 的棧中
            return autoreleaseFullPage(obj, page);
        } else {
            //2.3  無(wú) hotPage
            // 調(diào)用 autoreleaseNoPage 創(chuàng)建一個(gè) hotPage
            // 調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中
            return autoreleaseNoPage(obj);
        }
    }

從上面的注釋可以看到autoreleaseFast跟根據(jù)hotpage()執(zhí)行不同的流程

  • 1.有 hotPage 并且當(dāng)前 page 不滿

    調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中

    1. 有 hotPage 并且當(dāng)前 page 已滿

    調(diào)用 autoreleaseFullPage 初始化一個(gè)新的頁(yè)
    調(diào)用 page->add(obj) 方法將對(duì)象添加至新的 AutoreleasePoolPage 的棧中

  • 3.無(wú) hotPage

    調(diào)用 autoreleaseNoPage 創(chuàng)建一個(gè) hotPage
    調(diào)用 page->add(obj) 方法將對(duì)象添加至 AutoreleasePoolPage 的棧中

當(dāng)hotpPage 裝滿時(shí)刹悴,會(huì)調(diào)用autoreleaseFullPage函數(shù),實(shí)現(xiàn)如下:

    // 如果當(dāng)前的hotpage已經(jīng)裝滿
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // 對(duì)傳入的page對(duì)象條件檢查攒暇,必須是hotpage土匀,必須full
        // 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.
        // page 為hotpage
        ASSERT(page == hotPage());
        //  page full
        ASSERT(page->full()  ||  DebugPoolAllocation);
        // 通過(guò)page的 child指針,獲取下一個(gè)page對(duì)象形用,如果下一個(gè)page對(duì)象為空就轧,則調(diào)用AutoreleasePoolPage創(chuàng)建一個(gè)新的page對(duì)象
        // while 循環(huán)判斷page,直到page 沒(méi)有裝滿
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        // 將while循環(huán)得到的page對(duì)象設(shè)置為hotpage
        setHotPage(page);
        
        // 將obj對(duì)象加入到page中
        return page->add(obj);
    }

從上面的源碼中可以看出autoreleaseFullPage的實(shí)現(xiàn)邏輯:

    1. 傳入的page必須是hotpage田度,必須full妒御。
    1. 通過(guò) child指針,while遍歷每币,從當(dāng)前page尋找携丁,直到獲取到一個(gè)沒(méi)有裝滿的page,或者創(chuàng)建一個(gè)新的page兰怠。
  • 3.將while遍歷獲取到的page設(shè)置為hotPage梦鉴,并且將obj加入到page中

在上面的autoreleaseFast函數(shù)中我們通過(guò)hotPage()函數(shù)獲取到了最上層的page。

hotPage 實(shí)現(xiàn)
static inline AutoreleasePoolPage *hotPage()
{
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key); // 通過(guò) tls 查詢可用 AutoreleasePoolPage 對(duì)象
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 如果查詢結(jié)果為 EMPTY_POOL_PLACEHOLDER揭保,返回 nil
    if (result) result->fastcheck(); // 如果 result 不為空肥橙,則調(diào)用 fastcheck 方法,這里面做了檢測(cè)線程的工作
    return result;
}
自動(dòng)釋放池 full 實(shí)現(xiàn)
  bool full() { 
        return next == end();
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

判斷next指向是不是最后一個(gè)位置秸侣。

page->add(obj)實(shí)現(xiàn)

    // 將對(duì)象添加到自動(dòng)釋放池中存筏,壓棧操作
    id *add(id obj)
    {
        // page沒(méi)有裝滿
        ASSERT(!full());
        unprotect();
        // 獲取當(dāng)前的位置
        id *ret = next;  // faster than `return next-1` because of aliasing
        //將下一個(gè)位置指向obj
        *next++ = obj;
        protect();
        return ret;
    }

autoreleaseNoPage實(shí)現(xiàn)
//創(chuàng)建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()) {
            // 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.
            
       // 初始化第一個(gè)AutoreleasePoolPage,parent為nil
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        //設(shè)置為hotpage
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        // 先添加POOL_BOUNDARY 哨兵對(duì)象
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // 再將對(duì)象加入到自動(dòng)釋放池
        // Push the requested object or pool.
        return page->add(obj);
    }

既然當(dāng)前內(nèi)存中不存在 AutoreleasePoolPage味榛,就要從頭開(kāi)始構(gòu)建這個(gè)自動(dòng)釋放池的雙向鏈表椭坚,也就是說(shuō),新的 AutoreleasePoolPage 是沒(méi)有 parent 指針的搏色。

初始化之后善茎,將當(dāng)前頁(yè)標(biāo)記為 hotPage,然后會(huì)先向這個(gè) page 中添加一個(gè) POOL_SENTINEL 對(duì)象频轿,來(lái)確保在 pop 調(diào)用的時(shí)候垂涯,不會(huì)出現(xiàn)異常烁焙。

最后,將 obj 添加到自動(dòng)釋放池中耕赘。

三. objc_autoreleasePoolPop 方法

自動(dòng)釋放池結(jié)束時(shí)骄蝇,會(huì)調(diào)用析構(gòu)函數(shù)objc_autoreleasePoolPop。objc_autoreleasePoolPop實(shí)現(xiàn)如下:

void
_objc_autoreleasePoolPop(void *ctxt)
{
    objc_autoreleasePoolPop(ctxt);
}

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

通過(guò)runtime源碼發(fā)現(xiàn)操骡,最后調(diào)用的是AutoreleasePoolPage的pop函數(shù)九火,傳入的參數(shù)是POOL_SENTINEL哨兵對(duì)象

pop函數(shù)實(shí)現(xiàn)
 // 參數(shù)token:POOL_BOUNDARY的地址
    static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            // 獲取頂層的自動(dòng)釋放池
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                // page  為空,自動(dòng)釋放池還未使用册招,清空placeholder
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            // 將page設(shè)置為cold page
            page = coldPage();
            // token指向起始位置
            token = page->begin();
        } else {
            //通過(guò)POOL_BOUNDARY找到對(duì)應(yīng)的page
            page = pageForPointer(token);
        }

        //停止標(biāo)志
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            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);
        }
        // 調(diào)用popPage
        return popPage<false>(token, page, stop);
    }

在 pageForPointer函數(shù)中吃既,通過(guò)傳入的token(哨兵對(duì)象的地址),獲取到對(duì)應(yīng)的page跨细,然后執(zhí)行popPage函數(shù)釋放池中的對(duì)象

如何通過(guò)token找到當(dāng)前page

在上面說(shuō)到通過(guò)pageForPointer函數(shù)能獲取當(dāng)前page鹦倚,實(shí)現(xiàn)如下:

    // 通過(guò)哨兵對(duì)象的地址找到對(duì)應(yīng)的page
    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }

    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        // p :POOL_BOUNDARY 在對(duì)應(yīng)page中的地址
        AutoreleasePoolPage *result;
        
        //SIZE: 4096,每頁(yè)page的大小
        // p % SIZE : 獲取p 在page中的偏移地址
        uintptr_t offset = p % SIZE;

        // 邊界檢測(cè): 偏移地址 <= page 的內(nèi)存大小
        ASSERT(offset >= sizeof(AutoreleasePoolPage));
        
        //獲取p所在page的起始地址
        result = (AutoreleasePoolPage *)(p - offset);
        result->fastcheck();

        return result;
    }

從源碼中可以看到 將指針與頁(yè)面的大小,也就是 4096 取模冀惭,得到當(dāng)前指針的偏移量震叙,然后用指針地址減去偏移量就是page的起始地址

我們獲取到了當(dāng)前的page,就要調(diào)用 popPage去釋放自動(dòng)釋放池中的對(duì)象了散休。

popPage實(shí)現(xiàn)
 template<bool allowDebug>
    static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();
        
        //釋放page中的對(duì)象
        page->releaseUntil(stop);

        // memory: delete empty children 刪除空的子page
        // 1.當(dāng)前page為空媒楼,直接kill掉當(dāng)前page,然后把parent設(shè)置為hotpage
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            // 取出parent page
            AutoreleasePoolPage *parent = page->parent;
            // 釋放當(dāng)前page
            page->kill();
            // 設(shè)置parent page 為hotPage
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            // 2. 當(dāng)前page為空戚丸,而且沒(méi)有parent划址,kill掉當(dāng)前page,hotpage置為空限府;
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            //3.當(dāng)前page不為空夺颤,但是有child
            
            // 如果當(dāng)前page 的使用不超過(guò)一半
            if (page->lessThanHalfFull()) {
                // 從子 page開(kāi)始釋放
                page->child->kill();
            }
            else if (page->child->child) {
                // 如果當(dāng)前page的使用超過(guò)一半,并且還有 子子child(孫子 child)胁勺,則從孫子child開(kāi)始釋放
                // 這里如果當(dāng)前的page使用已經(jīng)超過(guò)一半世澜,意味著當(dāng)前page可能很快就要用完,這個(gè)時(shí)候就保留 child page,從孫子page開(kāi)始釋放署穗。創(chuàng)建page需要時(shí)間寥裂,這里犧牲一個(gè)page的內(nèi)存空間,提升了新能
                page->child->child->kill();
            }
        }
    }

1.當(dāng)前page為空案疲,直接kill掉當(dāng)前page封恰,然后把parent設(shè)置為hotpage;
2.當(dāng)前page為空褐啡,而且沒(méi)有parent诺舔,kill掉當(dāng)前page,hotpage置為空;
3.當(dāng)前page不為空混萝,但是有child,如果當(dāng)前page的空間占用不到一半萍恕,釋放child逸嘀,如果當(dāng)前page的空間占用超過(guò)一半,且child還有child允粤,直接釋放這個(gè)孫子輩的page崭倘。這里犧牲一個(gè)page的內(nèi)存空間,提升了新能

releaseUntil 釋放對(duì)象

releaseUntil 方法的實(shí)現(xiàn)如下:

 // 循環(huán)釋放對(duì)象类垫,直到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
        
        // while循環(huán)司光,直到next == stop
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            //獲取hotPage
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            // 如果page為空,利用parent指針找到一個(gè)不為空的page悉患,并設(shè)置為hotpage
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
            
            //將page所在的內(nèi)存區(qū)域設(shè)置為可讀可寫(xiě)
            page->unprotect();
            //通過(guò)next指針獲取page中記錄的對(duì)象残家,next -1 前移
            id obj = *--page->next;
            // void *memset(void *s, int ch, size_t n);
            // 將s中當(dāng)前位置后面的n個(gè)字節(jié) (typedef unsigned int size_t )用 ch 替換并返回 s 。
            // 將當(dāng)前對(duì)象的 8個(gè)字節(jié) 用0xA3 替換
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            // 修改完page中的值后售躁,設(shè)置page所在的內(nèi)存區(qū)域?yàn)橹蛔x
            page->protect();
            
            // 如果對(duì)象不是哨兵對(duì)象則釋放
            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }
        // 把當(dāng)前 page 設(shè)置 hotpage
        setHotPage(this);

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

使用while循環(huán)釋放page中的內(nèi)容坞淮,直到next == stop,通過(guò)memset底層函數(shù)陪捷,將釋放的地方用0xA3常量填充替換回窘。

releaseUntil函數(shù)只是將page中的對(duì)象 釋放了,并且對(duì)應(yīng)的位置用0xA3填充市袖,但是child/parent 指針沒(méi)有清空啡直,也就是說(shuō)page還在內(nèi)存中,沒(méi)有釋放苍碟。釋放page的操作在kill函數(shù)中進(jìn)行的

kill實(shí)現(xiàn)

kill 方法的實(shí)現(xiàn)如下:

// 清空child指針酒觅,釋放page
    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;
        // 通過(guò)while獲取尾部的page
        while (page->child) page = page->child;

        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->unprotect();
                // 清空child 指針,釋放子page
                page->child = nil;
                page->protect();
            }
            delete deathptr;
        } while (deathptr != this);
    }

kill會(huì)將當(dāng)前頁(yè)面以及子頁(yè)面全部刪除,釋放AutoreleasePoolPage占用空間微峰,是從最尾部的子page開(kāi)始釋放

調(diào)試自動(dòng)釋放池

可以通過(guò)_objc_autoreleasePooPrint()函數(shù)調(diào)試

// 聲明
extern void  _objc_autoreleasePooPrint();
// 執(zhí)行函數(shù)
_objc_autoreleasePoolPrint()
參考

自動(dòng)釋放池的前世今生 ---- 深入解析
黑幕背后的Autorelease
iOS 底層拾遺:AutoreleasePool

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阐滩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子县忌,更是在濱河造成了極大的恐慌掂榔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件症杏,死亡現(xiàn)場(chǎng)離奇詭異装获,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)厉颤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門穴豫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事精肃〕由” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵司抱,是天一觀的道長(zhǎng)筐眷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)习柠,這世上最難降的妖魔是什么匀谣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮资溃,結(jié)果婚禮上武翎,老公的妹妹穿的比我還像新娘。我一直安慰自己溶锭,他們只是感情好宝恶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著趴捅,像睡著了一般卑惜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驻售,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天露久,我揣著相機(jī)與錄音,去河邊找鬼欺栗。 笑死毫痕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的迟几。 我是一名探鬼主播消请,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼类腮!你這毒婦竟也來(lái)了臊泰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蚜枢,失蹤者是張志新(化名)和其女友劉穎缸逃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體厂抽,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡需频,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筷凤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昭殉。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挪丢,到底是詐尸還是另有隱情蹂风,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布乾蓬,位于F島的核電站惠啄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏巢块。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一巧号、第九天 我趴在偏房一處隱蔽的房頂上張望族奢。 院中可真熱鬧,春花似錦丹鸿、人聲如沸越走。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)廊敌。三九已至,卻和暖如春门怪,著一層夾襖步出監(jiān)牢的瞬間骡澈,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工掷空, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肋殴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓坦弟,卻偏偏與公主長(zhǎng)得像护锤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酿傍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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

  • 在上一篇文章中烙懦,詳細(xì)分析了IOS內(nèi)存管理的內(nèi)存布局、內(nèi)存管理方案赤炒、引用計(jì)數(shù)等內(nèi)容氯析,本篇文章將繼續(xù)上篇文章的內(nèi)容探索...
    風(fēng)緊扯呼閱讀 620評(píng)論 0 6
  • 什么是自動(dòng)釋放池 OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleasePool中的變量release...
    趙哥窟閱讀 5,316評(píng)論 8 9
  • 1.申明了一個(gè)對(duì)象__autoreleasepool 相當(dāng)于調(diào)用了objc_autoreleasePoolPush...
    開(kāi)洋_shen閱讀 540評(píng)論 0 0
  • 原作者原文鏈接:http://blog.sunnyxx.com/2014/10/15/behind-autorel...
    炒河粉兒閱讀 1,400評(píng)論 0 2
  • 簡(jiǎn)介 自動(dòng)釋放池(autoreleasepool)是OC的一種內(nèi)存自動(dòng)回收機(jī)制莺褒。正常情況下魄鸦,創(chuàng)建的變量超出作用域時(shí)...
    磊Se閱讀 1,821評(píng)論 1 8