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

自動(dòng)釋放池

自動(dòng)釋放池 是 OC 的一種 內(nèi)存自動(dòng)回收機(jī)制栋豫。它可以延遲加入 AutoreleasePool 中的變量 release 的時(shí)機(jī),即當(dāng)我們創(chuàng)建了一個(gè)對(duì)象虚茶,并把他加入到了自動(dòng)釋放池中時(shí)戈鲁,他不會(huì)立即被釋放,會(huì)等到一次 runloop 結(jié)束或者作用域超出 {} 或者超出 [pool release] 之后再被釋放嘹叫。下面我們通過(guò)三種方式分別來(lái)解析

Clang 分析

創(chuàng)建一個(gè)空工程婆殿,切換到 main.m 文件

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

通過(guò)終端命令 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk main.m 導(dǎo)出 cpp 文件,打開(kāi) main.cpp 文件罩扇,查看 main 函數(shù)的實(shí)現(xiàn)源碼

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

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

// __AtAutoreleasePool 結(jié)構(gòu)體
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  ~__AtAutoreleasePool() {
      objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

從上面源碼中看到婆芦,__AtAutoreleasePool 是一個(gè)結(jié)構(gòu)體,有 構(gòu)造函數(shù) + 析構(gòu)函數(shù)喂饥,結(jié)構(gòu)體定義的對(duì)象在作用域結(jié)束后消约,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)。本質(zhì)也是一個(gè)對(duì)象

@autoreleasepool {}
//等價(jià)于
{__AtAutoreleasePool __autoreleasepool; }

匯編分析

main 函數(shù)中打個(gè)斷點(diǎn)员帮,運(yùn)行程序或粮,開(kāi)啟匯編模式

通過(guò)調(diào)試的結(jié)果發(fā)現(xiàn),跟 clang 分析的結(jié)果一致

底層分析

關(guān)于 AutoreleasePool捞高,在 objc 源碼中有一段解釋

/***********************************************************************
   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
   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.
   The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased objects are stored. 
**********************************************************************/
    1. 線程的自動(dòng)釋放池是指針的堆棧氯材。
    1. 每個(gè)指針都是要釋放的對(duì)象(或者是 POOL_BOUNDARY,POOL_BOUNDARY 是自動(dòng)釋放池的邊界)硝岗。
    1. 一個(gè)自動(dòng)釋放池的標(biāo)示是指向該池的 POOL_BOUNDARY 的指針氢哮。 當(dāng)自動(dòng)釋放池出棧,將釋放比哨兵更熱的每個(gè)對(duì)象型檀。
    1. 堆棧被分為一個(gè)雙向鏈接的頁(yè)面列表(page)冗尤。 根據(jù)需要添加和刪除頁(yè)面。
    1. 線程本地存儲(chǔ)指向熱頁(yè)面胀溺,該頁(yè)面存儲(chǔ)新自動(dòng)釋放的對(duì)象裂七。

自動(dòng)釋放池是什么時(shí)候創(chuàng)建的?對(duì)象是如何加入自動(dòng)釋放池的月幌?哪些對(duì)象才會(huì)加入自動(dòng)釋放池碍讯?帶著這些疑問(wèn)悬蔽,我們來(lái)一步步探索自動(dòng)釋放池的底層原理

AutoreleasePoolPage

我們根據(jù)前面 clang 以及匯編的分析扯躺,自動(dòng)釋放池的底層是調(diào)用了 objc_autoreleasePoolPushobjc_autoreleasePoolPop 兩個(gè)方法,我們進(jìn)入 objc781 查看其源碼實(shí)現(xiàn),如下:

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

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

從上面的源碼中我們看到录语,兩個(gè)方法走的分別是 AutoreleasePoolPagepushpop 實(shí)現(xiàn)倍啥,那么進(jìn)入 AutoreleasePoolPage 的源碼看下是如何定義的

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
    
private:
    
    ...

    // 構(gòu)造函數(shù)
    AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
        AutoreleasePoolPageData(begin(),
                                objc_thread_self(),
                                newParent,
                                newParent ? 1+newParent->depth : 0,
                                newParent ? newParent->hiwat : 0) {...}

    // 析構(gòu)函數(shù)
    ~AutoreleasePoolPage(){...}
    
    ...

    // 添加釋放對(duì)象
    id *add(id obj) {...}

    //釋放所有對(duì)象
    void releaseAll() {...}
    
    ...

    // 獲取AutoreleasePoolPage
    static AutoreleasePoolPage *pageForPointer(const void *p) {...}
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) {...}

    ...

    // 獲取當(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)建新的page
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj) {...}

public:
    // 自動(dòng)釋放
    static inline id autorelease(id obj) {...}


    //入棧
    static inline void *push() {...}
    
    ...

    // 出棧
    static inline void
    pop(void *token) {...}

    ...
    
#undef POOL_BOUNDARY
};

AutoreleasePoolPage 定義發(fā)現(xiàn)虽缕,它是一個(gè) page,同時(shí)也是一個(gè) 對(duì)象蒲稳,這個(gè)頁(yè)的大小為 4096 字節(jié)氮趋。AutoreleasePoolPage 繼承自 AutoreleasePoolPageData,那么它的結(jié)構(gòu)是什么樣呢江耀?如下

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    // 用來(lái)校驗(yàn) AutoreleasePoolPage 的結(jié)構(gòu)是否完整
    magic_t const magic;
    // 指向最新添加的 autoreleased 對(duì)象的下一個(gè)位置剩胁,初始化時(shí)指向 begin()
    __unsafe_unretained id *next;
    // 指向當(dāng)前線程
    pthread_t const thread;
    // 指向父節(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil
    AutoreleasePoolPage * const parent;
    // 指向子節(jié)點(diǎn)祥国,最后一個(gè)結(jié)點(diǎn)的 child 值為 nil
    AutoreleasePoolPage *child;
    // 表示深度昵观,從 0 開(kāi)始,往后遞增 1
    uint32_t const depth;
    // 表示 high water mark 最大入棧數(shù)量標(biāo)記
    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)
    {
    }
};

AutoreleasePoolPageData 結(jié)構(gòu)看到了 AutoreleasePoolPage舌稀,這里也間接的證明了自動(dòng)釋放池是一個(gè) 雙向鏈表的頁(yè) 結(jié)構(gòu)啊犬。

AutoreleasePoolPageData 的內(nèi)存大小為 56 字節(jié),magic_t 結(jié)構(gòu)體占用內(nèi)存為 m[4]壁查,占內(nèi)存為 16 字節(jié)(4*4)觉至;屬性 next(指針)、thread(對(duì)象)睡腿、parent(對(duì)象)康谆、child(對(duì)象) 均占 8 字節(jié),共 32 字節(jié)嫉到;uint32_t 兩個(gè)各占 4 字節(jié)沃暗,共 8字節(jié)。

objc_autoreleasePoolPush

通過(guò)上面的分析何恶,我們進(jìn)入 AutoreleasePoolPagepush 實(shí)現(xiàn)孽锥,如下

static inline void *push()
{
    id *dest;
    if (slowpath(DebugPoolAllocation)) { //1.
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY); //2.
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);//3.
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
    1. 判斷是否創(chuàng)建過(guò) pool
    1. 沒(méi)有,創(chuàng)建一個(gè)新的 page
    1. 壓棧一個(gè) POOL_BOUNDARY(哨兵)

1. autoreleaseNewPage(創(chuàng)建新的頁(yè))

首先需要判斷 hotPage(當(dāng)前頁(yè))是否存在细层,執(zhí)行后續(xù)操作

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);
}

// 獲取當(dāng)前操作頁(yè)(hotPage)
static inline AutoreleasePoolPage *hotPage()
{
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    // 如果是個(gè)空池惜辑,返回 nil
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    //返回當(dāng)前線程的自動(dòng)釋放池
    if (result) result->fastcheck();
    return result;
}
  • 如果存在 hotpage,則通過(guò) autoreleaseFullPage 方法壓棧對(duì)象
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);

    // 當(dāng)前頁(yè)面滿了疫赎,遍歷循環(huán)子頁(yè)面
    do {
        // 如果子頁(yè)面存在盛撑,將當(dāng)前頁(yè)面替換為子頁(yè)面
        if (page->child) page = page->child;
        // 子頁(yè)面不存在,則新建
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

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

通過(guò)操作 child 對(duì)象捧搞,將當(dāng)前頁(yè)的 child 指向新建頁(yè)面抵卫,建立雙向鏈表連接

  • 如果不存在 hotpage狮荔,通過(guò) autoreleaseNoPage 創(chuàng)建頁(yè)
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è)空占位符
    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.

    // 初始化首頁(yè)
    // Install the first page.
    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) {
        // 壓棧哨兵對(duì)象
        page->add(POOL_BOUNDARY);
    }
    
    //壓棧對(duì)象
    // Push the requested object or pool.
    return page->add(obj);
}

當(dāng)前頁(yè)面不存在或者子頁(yè)面不存在時(shí)慨亲,通過(guò) AutoreleasePoolPage 的構(gòu)造方法創(chuàng)建新的 AutoreleasePoolPage婚瓜,而它的構(gòu)造方法實(shí)現(xiàn)是通過(guò) AutoreleasePoolPageData 的初始化方法來(lái)的

/**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,
                            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)
{
}
  • begin() 表示壓棧的位置(即下一個(gè)釋放對(duì)象的壓棧地址)闰渔。當(dāng)前頁(yè)面首地址+56(56 是 AutoreleasePoolPageData 的內(nèi)存大小)
id * begin() {
    // sizeof(*this) = 56
    return (id *) ((uint8_t *)this+sizeof(*this));
}
  • objc_thread_self() 表示當(dāng)前線程铐望,而當(dāng)前線程時(shí)通過(guò) tls 獲取的
__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 表示的是 AutoreleasePoolPageData 的父節(jié)點(diǎn)

  • newParent ? 1+newParent->depth : 0 表示通過(guò)父節(jié)點(diǎn)的深度計(jì)算 depth

  • newParent ? newParent->hiwat : 0 表示通過(guò)父節(jié)點(diǎn)的最大入棧個(gè)數(shù)計(jì)算 hiwat

  • add 方法

添加釋放對(duì)象冈涧,其底層是實(shí)現(xiàn)是通過(guò) next 指針存儲(chǔ)釋放對(duì)象,并將 next 指針遞增正蛙,表示下一個(gè)釋放對(duì)象存儲(chǔ)的位置督弓。從這里可以看出頁(yè)是通過(guò)棧結(jié)構(gòu)存儲(chǔ)

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;
}

2. autoreleaseFast(壓棧對(duì)象)

源碼實(shí)現(xiàn)如下

static inline id *autoreleaseFast(id obj)
{
    // 獲取當(dāng)前操作頁(yè)
    AutoreleasePoolPage *page = hotPage();
    // 當(dāng)前操作頁(yè)面存在愚隧,且頁(yè)面未存滿
    if (page && !page->full()) {
        return page->add(obj);
    } else if (page) {
        // 當(dāng)前操作頁(yè)面已經(jīng)存滿了
        return autoreleaseFullPage(obj, page);
    } else {
        // 當(dāng)前操作頁(yè)面不存在
        return autoreleaseNoPage(obj);
    }
}

3. 自動(dòng)釋放池內(nèi)存結(jié)構(gòu)

在 ARC 模式下,是無(wú)法手動(dòng)調(diào)用 autorelease锻全,所以要將項(xiàng)目切換至 MRC 模式 Build Settings -> Objective-C Automatic Reference Counting 設(shè)置為 NO

  • main.m 中添加如下代碼
// 打印自動(dòng)釋放池結(jié)構(gòu)
extern void _objc_autoreleasePoolPrint(void);

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] autorelease];
        }
        //調(diào)用
        _objc_autoreleasePoolPrint();
    }
}

運(yùn)行項(xiàng)目,打印結(jié)果如下

從打印結(jié)果我們看到有 6 個(gè)對(duì)象鳄厌,但是我們壓棧的對(duì)象是 5 個(gè)荞胡,另一個(gè)其實(shí)是前面說(shuō)到的哨兵對(duì)象(邊界),目的是為了防止越界了嚎。另外泪漂,從地址的打印,我們也看到了哨兵對(duì)象與首地址相差了 0x38(十進(jìn)制 56)剛好就是 AutoreleasePoolPage(繼承自 AutoreleasePoolPageData) 所占的內(nèi)存大小歪泳。

  • 將上述的 for 循環(huán)改為 505次萝勤,再次運(yùn)行項(xiàng)目,查看它的打印結(jié)果

從打印結(jié)果可以看到呐伞,第一頁(yè)已經(jīng)存滿了敌卓,存儲(chǔ)了 504 個(gè) 需要釋放的對(duì)象,第二頁(yè)存儲(chǔ)了一個(gè)對(duì)象伶氢,如果我們將 for 循環(huán)次數(shù)改為 1010 個(gè)呢趟径?

通過(guò)運(yùn)行發(fā)現(xiàn)瘪吏,第一頁(yè)存儲(chǔ) 504 個(gè),第二頁(yè)存儲(chǔ) 505 個(gè)舵抹,第三頁(yè)存儲(chǔ) 1 個(gè)

自動(dòng)釋放池第一頁(yè)可以存放 1 個(gè)哨兵對(duì)象(有且只有一個(gè),且在第一頁(yè))加 504 個(gè)需要釋放的對(duì)象劣砍,當(dāng)一頁(yè)壓棧滿了惧蛹,就會(huì)開(kāi)辟新的一頁(yè),從第二頁(yè)開(kāi)始可以存放最多 505 個(gè)對(duì)象(一頁(yè)的大小為 505*8 = 4040 字節(jié))

同樣這個(gè)結(jié)論可以通過(guò) AutoreleasePoolPageSIZE 來(lái)驗(yàn)證刑枝,定義中 PAGE_MIN_SIZE 大小為 4096 字節(jié)香嗓,在其構(gòu)造函數(shù)中對(duì)象的壓棧位置 begin() 是從 首地址+56 字節(jié)開(kāi)始的,所以在一個(gè) page 中實(shí)際可以存儲(chǔ) 4096-56 = 4040 字節(jié)装畅,轉(zhuǎn)換成對(duì)象 4040/8 = 505 個(gè)靠娱,因?yàn)榈谝豁?yè)有哨兵對(duì)象,最多存儲(chǔ) 504 個(gè)

objc_autoreleasePoolPop

objc-781 源碼中我們看到 objc_autoreleasePoolPop 實(shí)現(xiàn)源碼中有個(gè)參數(shù)掠兄,這個(gè)參數(shù)在 clang 分析中可以找到像云,在 objc_autoreleasePoolPush() 返回一個(gè) atautoreleasepoolobj (哨兵對(duì)象),即 ctxt蚂夕。其目的是為了避免出椦肝埽混亂。

  • 進(jìn)入 pop 源碼婿牍,實(shí)現(xiàn)如下
static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
    // 判斷對(duì)象是否是空占位符
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        // 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 的起始位置
        page = coldPage();
        token = page->begin();
    } else {
        // 獲取 token 所在的頁(yè)
        page = pageForPointer(token);
    }

    stop = (id *)token;
    // 判斷是否是哨兵對(duì)象
    if (*stop != POOL_BOUNDARY) {
        if (stop == page->begin()  &&  !page->parent) {
            // 如果是當(dāng)前頁(yè)的第一個(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 {
            // 否則上遥,返回混亂
            // 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);
}
    1. 空白頁(yè)處理/根據(jù)token獲取page
    1. 容錯(cuò)處理
    1. popPage 出棧

進(jìn)入 popPage 的源碼搏屑。通過(guò) releaseUntil 出棧當(dāng)前頁(yè) stop 位置之前的所有對(duì)象,即向棧中的對(duì)象發(fā)送 release 消息粉楚,直到遇到傳入的哨兵對(duì)象睬棚。

template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    if (allowDebug && PrintPoolHiwat) printHiwat();

    // 當(dāng)前操作頁(yè)面出棧
    page->releaseUntil(stop);

    // memory: delete empty children
    // 特殊情況處理 allowDebug 傳入為 fasle
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        // 獲取當(dāng)前頁(yè)面的父節(jié)點(diǎn)
        AutoreleasePoolPage *parent = page->parent;
        // 將當(dāng)前頁(yè)面 kill
        page->kill();
        // 設(shè)置父頁(yè)面為當(dāng)前操作頁(yè)
        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();
        }
    }
}

進(jìn)入 releaseUntil 實(shí)現(xiàn),源碼如下

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解幼,不等于抑党,一直 while 循環(huán)
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release
        // autoreleased more objects
        // 獲取當(dāng)前操作頁(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)
            page = page->parent;
            // 將父節(jié)點(diǎn)設(shè)置為當(dāng)前操作頁(yè)
            setHotPage(page);
        }

        page->unprotect();
        // next 進(jìn)行 -- 操作,出棧
        id obj = *--page->next;
        // 將已開(kāi)辟內(nèi)存空間 page->next 的首 sizeof(*page->next) 個(gè)字節(jié)的值設(shè)為值 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
}
    1. 通過(guò) do-while 循環(huán)底靠,判斷對(duì)象是否等于 stop,目的是釋放 stop 之前的所有需要釋放的對(duì)象
    1. 判空處理
    1. 通過(guò)獲取 page 的 next 對(duì)象特铝,標(biāo)記已被釋放狀態(tài)
    1. 判斷是否是哨兵對(duì)象暑中,如果不是則自動(dòng)調(diào)用 objc_release 釋放

下面我們進(jìn)入 kill 的源碼壹瘟,實(shí)現(xiàn)如下

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è) page
    while (page->child) page = page->child;

    AutoreleasePoolPage *deathptr;
    do {
        deathptr = page;
        // 當(dāng)前頁(yè)的子節(jié)點(diǎn)變?yōu)楦腹?jié)點(diǎn)
        page = page->parent;
        if (page) {
            page->unprotect();
            // 將父節(jié)點(diǎn)頁(yè)的子節(jié)點(diǎn)變?yōu)?nil
            page->child = nil;
            page->protect();
        }
        delete deathptr;
    } while (deathptr != this);
}

主要是銷(xiāo)毀當(dāng)前頁(yè),將當(dāng)前頁(yè)賦值為父節(jié)點(diǎn)頁(yè)鳄逾,并將父節(jié)點(diǎn)頁(yè)的 child 對(duì)象指針置為 nil

autorelease 源碼分析

上面我們知道了 autoreleasepool 的底層原理稻轨,下面我們來(lái)看下 autorelease 的底層實(shí)現(xiàn),如下雕凹,我們打開(kāi)匯編看下 autorelease 的底層調(diào)用(因?yàn)?Xcode 默認(rèn)是 ARC 模式殴俱,不能調(diào)用 autorelease,需要開(kāi)啟 MRC 模式)

打開(kāi) objc781 源碼枚抵,我們進(jìn)入 objc_autorelease 的源碼线欲,如下

__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
    // 如果不是對(duì)象,直接返回
    if (!obj) return obj;
    // 如果是 Tagged Pointer汽摹,直接返回
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

繼續(xù)往下走

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

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}

inline id 
objc_object::rootAutorelease()
{
    // 如果是 Tagged Pointer李丰,返回
    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());
        // 壓棧操作
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

無(wú)論是壓棧哨兵對(duì)象,還是普通對(duì)象逼泣,都會(huì)來(lái)到 autoreleaseFast方法趴泌,只是區(qū)別標(biāo)識(shí)不同而以

總結(jié)

通過(guò)以上的分析,針對(duì)自動(dòng)釋放池的 push 以及 pop拉庶,做個(gè)總結(jié)

objc_autoreleasePoolPush

    1. 判斷有沒(méi)有 pool踱讨,即只有空占位符(存儲(chǔ)在tls中)時(shí),創(chuàng)建頁(yè)砍的,并壓棧哨兵對(duì)象
    1. 壓棧對(duì)象痹筛,通過(guò) page->add(obj) 方法,將 next 指針遞增
    1. 當(dāng)頁(yè)面滿了廓鞠,設(shè)置當(dāng)前操作頁(yè)的 child 為新建頁(yè)帚稠,并設(shè)置新建頁(yè)為當(dāng)前操作頁(yè),壓棧對(duì)象

push 流程圖

objc_autoreleasePoolPop

    1. 在頁(yè)中出棧床佳,主要是通過(guò) next 指針遞減實(shí)現(xiàn)
    1. 當(dāng)頁(yè)空了時(shí)滋早,賦值頁(yè)的 parent 為當(dāng)前操作頁(yè),并將新的當(dāng)前操作頁(yè)的 child 設(shè)置為 nil

pop 流程圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末砌们,一起剝皮案震驚了整個(gè)濱河市杆麸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浪感,老刑警劉巖昔头,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異影兽,居然都是意外死亡揭斧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)峻堰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)讹开,“玉大人盅视,你說(shuō)我怎么就攤上這事〉┩颍” “怎么了闹击?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)成艘。 經(jīng)常有香客問(wèn)我赏半,道長(zhǎng),這世上最難降的妖魔是什么狰腌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任除破,我火速辦了婚禮牧氮,結(jié)果婚禮上琼腔,老公的妹妹穿的比我還像新娘。我一直安慰自己踱葛,他們只是感情好丹莲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著尸诽,像睡著了一般甥材。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上性含,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天洲赵,我揣著相機(jī)與錄音,去河邊找鬼商蕴。 笑死叠萍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绪商。 我是一名探鬼主播苛谷,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼格郁!你這毒婦竟也來(lái)了腹殿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤例书,失蹤者是張志新(化名)和其女友劉穎锣尉,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體决采,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悟耘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了织狐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暂幼。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筏勒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旺嬉,到底是詐尸還是另有隱情管行,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布邪媳,位于F島的核電站捐顷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雨效。R本人自食惡果不足惜迅涮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徽龟。 院中可真熱鬧叮姑,春花似錦、人聲如沸据悔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)极颓。三九已至朱盐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菠隆,已是汗流浹背兵琳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骇径,地道東北人躯肌。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像既峡,于是被迫代替她去往敵國(guó)和親羡榴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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