自動釋放池與@autoreleasepool

一、Autoreleasepool概念

  • 自動釋放池(autorelease pool)是OC的一種內(nèi)存自動回收機制. 具有延遲釋放的特性,即當(dāng)我們創(chuàng)建了一個對象,并把他加入到了自動釋放池中時,他不會立即被釋放,會等到一次runloop結(jié)束或者作用域超出{}或者超出[pool release]之后再被釋放
  • 當(dāng)你向一個對象發(fā)送一個autorelease消息的時候,Cocoa就會將對象的一個引用放入
    到最新的自動釋放池中(當(dāng)前線程棧頂位置),它仍然是一個對象,因此自動釋放池定義的作用域內(nèi)的其他對象都可以向他發(fā)送消息.一個自動釋放池存儲的對象當(dāng)自己被銷毀的時會向其中的對象發(fā)送 release 消息。

二、Autoreleasepool創(chuàng)建和銷毀時機

MRC

  • 通過手動創(chuàng)建的方式來創(chuàng)建自動釋放池,這種方式創(chuàng)建的自動釋放池需要手動調(diào)用release(引用計數(shù)環(huán)境下,調(diào)用release和drain的效果相同,但是在CG下,drain會觸發(fā)GC,release不會),方法如下:
NSAutoreleasePool *pool = [[ NSAutoreleasePool alloc]init ];//創(chuàng)建一個自動釋放池
    Person *person = [[Person alloc]init];
    //調(diào)autorelease方法將對象加入到自動釋放池
    //注意使用該方法,對象不會自己加入到自動釋放池,需要人為調(diào)用autorelease方法加入
    [person autorelease];
    //,手動釋放自動釋放池執(zhí)行完這行代碼是,自動釋放池會對加入他中的對象做一次release操作
    [pool release];

自動釋放池銷毀時機:[pool release]代碼執(zhí)行完后

  • 通過@autoreleasepool來創(chuàng)建
    • 對象的創(chuàng)建在自動釋放池里面
@autoreleasepool {
        //在這個{}之內(nèi)的變量默認被添加到自動釋放池
         Person *p = [[Person alloc] init];
      }//除了這個括號,p被釋放
    • 如果一個變量在自動釋放池之外創(chuàng)建,如下,需要通過__autoreleasing該修飾符將其加入到自動釋放池。
@autoreleasepool {

}
Person *   __autoreleasing p = [
[Person alloc]init];
 self.person = p;

系統(tǒng)就是通過@autoreleasepool {}這種方式來為我們創(chuàng)建自動釋放池的,一個線程對應(yīng)一個runloop,系統(tǒng)會為每一個runloop隱式的創(chuàng)建一個自動釋放池,所有的autoreleasePool構(gòu)成一個棧式結(jié)構(gòu),在每個runloop結(jié)束時,當(dāng)前棧頂?shù)腶utoreleasePool會被銷毀,而且會對其中的每一個對象做一次release(嚴格來說,是你對這個對象做了幾次autorelease就會做幾次release,不一定是一次),特別指出,使用容器的block版本的枚舉器的時候,系統(tǒng)會自動添加一個autoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個局部@autoreleasepool包圍著
}];

ARC

ARC下除了NSAutoreleasePool不可用以外,其他的同MRC

三、Autoreleasepool 應(yīng)用場景

MRC:

  • 對象作為函數(shù)返回值
    • 當(dāng)一個對象要作為函數(shù)返回值的時候,因為要遵循誰申請誰釋放的思想,所以應(yīng)該在返回之前釋放,但要是返回之前釋放了,就會造成野指針錯誤,但是要是不釋放,那么就違背了誰申請誰釋放的原則,所以就可以使用autorelease延遲釋放的特性,將其在返回之前做一次autorelease,加入到自動釋放池中,保證可以被返回,一次runloop之>>后系統(tǒng)會幫我們釋放他
  • 自動釋放池可以延長對象的聲明周期,如果一個事件周期很長,比如有一個很長的循環(huán)邏輯,那么一個臨時變量可能很長時間都不會被釋放,一直在內(nèi)存中保留,那么內(nèi)存的峰值就會一直增加,但是其實這個臨時變量是我們不再需要的。這個時候就通過創(chuàng)建新的自動釋放池來縮短臨時變量的生命周期來降低內(nèi)存的峰值。
  • 臨時生成大量對象,一定要將自動釋放池放在for循環(huán)里面,要釋放在外面,就會因為大量對象得不到及時釋放,而造成內(nèi)存緊張,最后程序意外退出
for (int i = 0; i<10000; i++) {
        @autoreleasepool {
            UIImageView *imegeV = [[UIImageView alloc]init];
            imegeV.image = [UIImage imageNamed:@"efef"];
            [self.view addSubview:imegeV];
        }
    }
    

嵌套的autoreleasepool

@autoreleasepool {//p1被放在該自動釋放池里面
        Person *p1 = [[Person alloc]init];
        @autoreleasepool {//p2被放在該自動釋放池里面
            Person *p2 = [[Person alloc]init];
        }
    }

以上說明autorelease對象被添加進離他最近的自動釋放池,多層的pool會有多個哨兵對象。

四、Autoreleasepool 底層實現(xiàn)

Autoreleasepool實現(xiàn)原理

在了解自動釋放池的原理之前,我們先來看一下以下幾個概念

AutoreleasePoolPage

  • ARC下,我們使用@autoreleasepool{}來使用一個AutoreleasePool,隨后編譯器將其改寫成下面的樣子:
void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);
  • 而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝,所以自動釋放機制的核心就在于這個類。

AutoreleasePoolPage是一個C++實現(xiàn)的類


圖1.jpg
  • AutoreleasePoolPage:每一個自動釋放池沒有單獨的結(jié)構(gòu),每一個autorealeasePool對象都是由若干個autoreleasePoolPage通過雙向鏈表連接而成,類的定義如下
class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;//指向棧頂最新被添加進來的autorelease對象的下一個位置
    pthread_t const thread;//指向當(dāng)前線程
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t 
  }
  • AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
  • AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小),除了上面的實例變量所占空間,剩下的空間全部用來儲存autorelease對象的地址
  • 上面的id *next指針作為游標(biāo)指向棧頂最新add進來的autorelease對象的下一個位置
  • 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象,連接鏈表,后來的autorelease對象在新的page加入

所以,若當(dāng)前線程中只有一個AutoreleasePoolPage對象,并記錄了很多autorelease對象地址時內(nèi)存如下圖:

圖2.jpg

圖中的情況,這一頁再加入一個autorelease對象就要滿了(也就是next指針馬上指向棧頂),這時就要執(zhí)行上面說的操作,建立下一頁page對象,與這一頁鏈表連接完成后,新page的next指針被初始化在棧底(begin的位置),然后繼續(xù)向棧頂添加新對象。

所以,向一個對象發(fā)送- autorelease消息,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置

哨兵對象

  • 本質(zhì)是一個值為nil的對象,由被定義在* NSAutoreleasePool類中的_token指針來保存。

hotPage

  • 最新創(chuàng)建的AutoreleasePoolPage.

原理

系統(tǒng)通過一個棧來管理所有的自動釋放池,每當(dāng)創(chuàng)建了一個新的自動釋放池,系統(tǒng)就會把它壓入棧頂,并且傳入一個哨兵對象,將哨兵對象插入hotPage,這里分三種情況

  • 若hotPage未滿,則直接插入哨兵對象,
  • 要是滿了,新建一個NSAutoreleasePoolPage,并將其作為hotPage,然后將哨兵對象插入
  • 如果沒有NSAutoreleasePoolPage,則新建一個NSAutoreleasePoolPage,并將其作為hotPage,插入哨兵對象,注意。這里的hotPage是沒有父節(jié)點的。

每當(dāng)有一個自動釋放池要被釋放的時候,哨兵對象就會作為參數(shù)被傳入,找到該哨兵對象所在的位置后,將所有晚于哨兵對象的autorelease彈出,并對他們做一次release,然后將next指針一到合適的位置。

Autoreleasepool底層實現(xiàn)

  • 首先我們?nèi)タ梢圆榭匆幌拢琧lang 轉(zhuǎn)成 c++ 的 autoreleasepool 的源碼:
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

可以發(fā)現(xiàn)objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop() 這兩個方法。

  • 再看一下runtime 中 Autoreleasepool 的結(jié)構(gòu),通過閱讀源碼可以看出 Autoreleasepool 是一個由 AutoreleasepoolPage 雙向鏈表的結(jié)構(gòu),其中 child 指向它的子 page,parent 指向它的父 page。
雙向鏈表結(jié)構(gòu)圖.png
  • 并且每個 AutoreleasepoolPage 對象的大小都是 4096 個字節(jié)。
#define PAGE_MAX_SIZE           PAGE_SIZE
#define PAGE_SIZE       I386_PGBYTES
#define I386_PGBYTES        4096        /* bytes per 80386 page */

  • AutoreleasepoolPage 通過壓棧的方式來存儲每個需要自動釋放的對象。
//入棧方法
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            //在 Debug 情況下每一個自動釋放池 都以一個新的 poolPage 開始
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
//正常情況下,調(diào)用 push 方法會先插入一個 POOL_BOUNDARY 標(biāo)志位
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

  • 然后我們來看看 runtime 中的源碼
/***********************************************************************
 自動釋放池的實現(xiàn):
 一個線程的自動釋放池是一個指針堆棧
 每一個指針或者指向被釋放的對象,或者是自動釋放池的 POOL_BOUNDARY,POOL_BOUNDARY 是自動釋放池的邊界。
 一個池子的 token 是指向池子 POOL_BOUNDARY 的指針。當(dāng)池子被出棧的時候,每一個高于標(biāo)準的對象都會被釋放掉。
 堆棧被分成一個頁面的雙向鏈表。頁面按照需要添加或者刪除。
 本地線程存放著指向當(dāng)前頁的指針,在這里存放著新創(chuàng)建的自動釋放的對象。
**********************************************************************/
// Set this to 1 to mprotect() autorelease pool contents
//將這個設(shè)為 1 可以通過mprotect修改映射存儲區(qū)的權(quán)限來更改自動釋放池的內(nèi)容
#define PROTECT_AUTORELEASEPOOL 0
#define CHECK_AUTORELEASEPOOL (DEBUG)
BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));
namespace {
//對AutoreleasePoolPage進行完整性校驗
struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;
    uint32_t m[4];    
    magic_t() {
        assert(M1_len == strlen(M1));
        assert(M1_len == 3 * sizeof(m[1]));
        m[0] = M0;
        strncpy((char *)&m[1], M1, M1_len);
    }
    ~magic_t() {
        m[0] = m[1] = m[2] = m[3] = 0;
    }
    bool check() const {
        return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len));
    }
    bool fastcheck() const {
#if CHECK_AUTORELEASEPOOL
        return check();
#else
        return (m[0] == M0);
#endif
    }
#   undef M1
};
//自動釋放頁
class AutoreleasePoolPage 
{
  //EMPTY_POOL_PLACEHOLDER 被存放在本地線程存儲中當(dāng)一個池入棧并且沒有存放任何對象的時候。這樣在棧頂入棧出棧并且沒有使用它們的時候會節(jié)省內(nèi)存。
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    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
  //對象數(shù)量
    static size_t const COUNT = SIZE / sizeof(id);
//校驗完整性
    magic_t const magic;
//頁中對象的下一位索引
    id *next;
//線程
    pthread_t const thread;
//父頁
    AutoreleasePoolPage * const parent;
//子頁
    AutoreleasePoolPage *child;
//深度
    uint32_t const depth;
    uint32_t hiwat;
    // SIZE-sizeof(*this) bytes of contents follow
//創(chuàng)建
    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
//刪除
    static void operator delete(void * p) {
        return free(p);
    }
//設(shè)置當(dāng)前內(nèi)存可讀
    inline void protect() {
#if PROTECT_AUTORELEASEPOOL
        mprotect(this, SIZE, PROT_READ);
        check();
#endif
    }
//設(shè)置當(dāng)前內(nèi)存可讀可寫
    inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
        check();
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }
  //初始化
    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();
    }
//析構(gòu)
    ~AutoreleasePoolPage() 
    {
        check();
        unprotect();
        assert(empty());
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        assert(!child);
    }
//被破壞的
    void busted(bool die = true) 
    {
        magic_t right;
        (die ? _objc_fatal : _objc_inform)
            ("autorelease pool page %p corrupted\n"
             "  magic     0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  should be 0x%08x 0x%08x 0x%08x 0x%08x\n"
             "  pthread   %p\n"
             "  should be %p\n", 
             this, 
             magic.m[0], magic.m[1], magic.m[2], magic.m[3], 
             right.m[0], right.m[1], right.m[2], right.m[3], 
             this->thread, pthread_self());
    }
//校驗
    void check(bool die = true) 
    {
        if (!magic.check() || !pthread_equal(thread, pthread_self())) {
            busted(die);
        }
    }
//快速校驗
    void fastcheck(bool die = true) 
    {
#if CHECK_AUTORELEASEPOOL
        check(die);
#else
        if (! magic.fastcheck()) {
            busted(die);
        }
#endif
    }
//頁的開始位置
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
//頁的結(jié)束位置
    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }
//頁是否是空的
    bool empty() {
        return next == begin();
    }
//頁是否是滿的
    bool full() { 
        return next == end();
    }
//是否少于一半
    bool lessThanHalfFull() {
        return (next - begin() < (end() - begin()) / 2);
    }
//添加對象
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
//釋放所有對象
    void releaseAll() 
    {
        releaseUntil(begin());
    }
//釋放到 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 (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();
            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }
            page->unprotect();
            id obj = *--page->next;
//將頁索引內(nèi)容置為 SCRIBBLE 表示已經(jīng)被釋放
            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
    }
//殺死
    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);
    }
//釋放本地線程存儲空間
    static void tls_dealloc(void *p) 
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }
        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);
        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) pop(page->begin());  // pop all of the pools
            if (DebugMissingPools || DebugPoolAllocation) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }
//獲取 AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        uintptr_t offset = p % SIZE;
        assert(offset >= sizeof(AutoreleasePoolPage));
        result = (AutoreleasePoolPage *)(p - offset);
        result->fastcheck();
        return result;
    }
//是否有空池占位符
    static inline bool haveEmptyPoolPlaceholder()
    {
        id *tls = (id *)tls_get_direct(key);
        return (tls == EMPTY_POOL_PLACEHOLDER);
    }
//設(shè)置空池占位符
    static inline id* setEmptyPoolPlaceholder()
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
//獲取當(dāng)前頁
    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
//設(shè)置當(dāng)前頁
    static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck();
        tls_set_direct(key, (void *)page);
    }
//獲取 coldPage
    static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }
//快速釋放
    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);
        }
    }
//添加自動釋放對象,當(dāng)頁滿的時候調(diào)用這個方法
    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);
    }
//添加自動釋放對象,當(dāng)沒頁的時候使用這個方法
    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", 
                         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();
        }
        // 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);
    }
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
//公開方法
public:
//自動釋放
    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;
    }
//入棧
    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
//兼容老的 SDK 出棧方法
    static void badPop(void *token)
    {
        // Error. For bincompat purposes this is not 
        // fatal in executables built with old SDKs.
        if (DebugPoolAllocation  ||  sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) {
            // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal.
            _objc_fatal
                ("Invalid or prematurely-freed autorelease pool %p.", token);
        }
        // Old SDK. Bad pop is warned once.
        static bool complained = false;
        if (!complained) {
            complained = true;
            _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);
    }
//出棧
    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (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);
            }
            return;
        }
        page = pageForPointer(token);
        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);
            }
        }
//打印 hiwat
        if (PrintPoolHiwat) printHiwat();
        page->releaseUntil(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) {
            // 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();
            }
        }
    }
    static void init()
    {
        int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                             AutoreleasePoolPage::tls_dealloc);
        assert(r == 0);
    }
//打印
    void print() 
    {
        _objc_inform("[%p]  ................  PAGE %s %s %s", this, 
                     full() ? "(full)" : "", 
                     this == hotPage() ? "(hot)" : "", 
                     this == coldPage() ? "(cold)" : "");
        check(false);
        for (id *p = begin(); p < next; p++) {
            if (*p == POOL_BOUNDARY) {
                _objc_inform("[%p]  ################  POOL %p", p, p);
            } else {
                _objc_inform("[%p]  %#16lx  %s", 
                             p, (unsigned long)*p, object_getClassName(*p));
            }
        }
    }
//打印所有
    static void printAll()
    {        
        _objc_inform("##############");
        _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self());
        AutoreleasePoolPage *page;
        ptrdiff_t objects = 0;
        for (page = coldPage(); page; page = page->child) {
            objects += page->next - page->begin();
        }
        _objc_inform("%llu releases pending.", (unsigned long long)objects);
        if (haveEmptyPoolPlaceholder()) {
            _objc_inform("[%p]  ................  PAGE (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
            _objc_inform("[%p]  ################  POOL (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
        }
        else {
            for (page = coldPage(); page; page = page->child) {
                page->print();
            }
        }

        _objc_inform("##############");
    }
//打印 hiwat 
    static void printHiwat()
    {
        // Check and propagate high water mark
        // Ignore high water marks under 256 to suppress noise.
        AutoreleasePoolPage *p = hotPage();
        uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
        if (mark > p->hiwat  &&  mark > 256) {
            for( ; p; p = p->parent) {
                p->unprotect();
                p->hiwat = mark;
                p->protect();
            }
            _objc_inform("POOL HIGHWATER: new high water mark of %u "
                         "pending releases for thread %p:", 
                         mark, pthread_self());
            void *stack[128];
            int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
            char **sym = backtrace_symbols(stack, count);
            for (int i = 0; i < count; i++) {
                _objc_inform("POOL HIGHWATER:     %s", sym[i]);
            }
            free(sym);
        }
    }
#undef POOL_BOUNDARY
};

Autoreleasepool的優(yōu)化

ARC下autorelease的優(yōu)化

ARC下,runtime提供了一套對autorelease返回值的優(yōu)化策略TLS(線程局部存儲),就是為每個線程單獨分配一塊棧控件。以key-value的形式對數(shù)據(jù)進行存儲(ARC對autorelease對象的優(yōu)化標(biāo)記)。

先看優(yōu)化中涉及到的幾個函數(shù)

1、__builtin_return_address

是一個內(nèi)建函數(shù),作用是返回函數(shù)的地址,參數(shù)是層級,如果是0,則表示是當(dāng)前函數(shù)體返回地址;如果是1:則表示調(diào)用這個函數(shù)的外層函數(shù)。當(dāng)我們知道了一個函數(shù)體的返回地址的時候,就可以根據(jù)反匯編,利用某些固定的偏移量,被調(diào)方可以定位到主調(diào)放在返回值后>面的匯編指令,來確定該條函數(shù)指令調(diào)用完成后下一個調(diào)用的函數(shù)

  • 這個內(nèi)建函數(shù)原型是char
  • __builtin_return_address(int level),作用是得到函數(shù)的返回地址,參數(shù)表示層數(shù),如__builtin_return_address(0)表示當(dāng)前函數(shù)體返回地址,傳1是調(diào)用這個函數(shù)的外層函數(shù)的返回值地址,以此類推。
- (int)foo {
    NSLog(@"%p", __builtin_return_address(0)); // 根據(jù)這個地址能找到下面ret的地址
    return 1;
}
// caller
int ret = [sark foo];

2、Thread Local Storage

Thread Local Storage(TLS)線程局部存儲,目的很簡單,將一塊內(nèi)存作為某個線程專有的存儲,以key-value的形式進行讀寫,比如在非arm架構(gòu)下,使用pthread提供的方法實現(xiàn):

void* pthread_getspecific(pthread_key_t);
int pthread_setspecific(pthread_key_t , const void *);

在返回值身上調(diào)用objc_autoreleaseReturnValue方法時,runtime將這個返回值object儲存在TLS中,然后直接返回這個object(不調(diào)用autorelease);同時,在外部接收這個返回值的objc_retainAutoreleasedReturnValue里,發(fā)現(xiàn)TLS中正好存了這個對象,那么直接返回這個object(不調(diào)用retain)。
于是乎,調(diào)用方和被調(diào)方利用TLS做中轉(zhuǎn),很有默契的免去了對返回值的內(nèi)存管理。

  • objc_autoreleaseReturnValue

通過__builtin_return_address(int level)檢測外部是不是ARC環(huán)境,可以替代autorelease,是的話直接返回object,不是的話調(diào)用objc_autorelease()將對象注冊到自動釋放池里面,最后通過objc_retain來獲取對象。

  • objc_retainAutoreleasedReturnValue

與objc_autoreleaseReturnValue配合使用,如果在執(zhí)行完objc_autoreleaseReturnValue()這個函數(shù)的下一個調(diào)用函數(shù)是objc_retainAutoreleasedReturnValue,那么就走最優(yōu)化(在TLS中查詢關(guān)于這個對象,如果有,直接返回對象,不再對對象做retain)。

  • 在調(diào)用objc_autoreleaseReturnValue的時候,會先通過__builtin_return_address這個內(nèi)建函數(shù)return address,然后根據(jù)這個地址判斷主調(diào)方在調(diào)用完objc_autoreleaseReturnValue這個函數(shù)以后是否緊接著調(diào)用了objc_retainAutoreleasedReturnValue函數(shù),如果是,那么objc_autoreleaseReturnValue()就不將返回的對象注冊到自動釋放池里面(不做autorelease),runtime會將這個返回值存儲在TLS中,做一個優(yōu)化標(biāo)記,然后直接返回這個對象給函數(shù)的調(diào)用方,在外部接收這個返回值的objc_retainAutoreleasedReturnValue()會先在TLS中查詢有沒有這個對象,如果有,那么就直接返回這個對象(不調(diào)用retain),所以通過objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue的相互配合,利用TSL做一個中轉(zhuǎn),在ARC下省去了autorelease和retain的步驟,在一定程度上達到了最優(yōu)化.

六、自動釋放池常見面試題代碼

for (int i = 0; i < 100000; ++i) {
        NSString *str = @"Hello World";
        str = [str stringByAppendingFormat:@"- %d",i];  //字符串拼接
        str = [str uppercaseString];   //將字符串替換成大寫
    }

如果循環(huán)的次數(shù)過大,會出現(xiàn)什么問題?該怎么解決?

答案:

會出現(xiàn)內(nèi)存溢出,循環(huán)內(nèi)部創(chuàng)建大量的臨時對象,沒有被釋放

每次循環(huán)都將上一次創(chuàng)建的對象release

for (int i = 0; i < 100000; ++i) {
       @autoreleasepool{
        NSString *str = @"Hello World";
        str = [str stringByAppendingFormat:@"- %d",i];
        str = [str uppercaseString];
    }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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