從源碼角度看蘋果是如何實現 autorelease 和 autoreleasepool 的

書接上文,接下來讓咱們瞭解一下,蘋果是如何實現 autorelease 和 autoreleasepool 的架構

autorelease

autorelease 方法的做用是延遲對象的 release,一般用於返回值時使用,以下是它的實現:ide

// Replaced by ObjectAlloc
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

// Base autorelease implementation, ignoring overrides.
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this; // 若是是 tagged pointer,直接返回 this
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; // 若是 prepareOptimizedReturn(ReturnAtPlus1) 返回 true,直接返回 this

    return rootAutorelease2(); // 調用 rootAutorelease2
}
複製代碼

prepareOptimizedReturn

prepareOptimizedReturn 是用於優化返回的,其實現以下函數

// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0); // 確保當前獲取的 Disposition 爲 false

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {  // 若是當前容許優化返回值
        if (disposition) setReturnDisposition(disposition); // 設置 ReturnDisposition 爲 true
        return true;
    }

    return false;
}
複製代碼

ReturnDisposition & setReturnDisposition

ReturnDisposition & setReturnDisposition 是用於獲取和設置 Disposition 用的函數,實現以下優化

static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}

static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) {
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
複製代碼

tls,全程爲 Thread Local Storage,是在當前線程存儲一些數據用的,在這裏是經過 RETURN_DISPOSITION_KEY 這個 key 進行存儲的,其實現以下:ui

typedef pthread_key_t tls_key_t;

#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif

複製代碼

__builtin_return_address

gcc 的編譯特性使用 __builtin_return_address(level) 打印出一個函數的堆棧地址。其中 level 表明是堆棧中第幾層調用地址,__builtin_return_address(0) 表示第一層調用地址,即當前函數,__builtin_return_address(1) 表示第二層。this

callerAcceptsOptimizedReturn

callerAcceptsOptimizedReturn 是用來判斷當前返回是否能夠優化的函數,其在不一樣架構上的實現不一樣,我暫時沒搞懂這段代碼究竟是什麼意思,等我哪天想清楚了再補上這個坑吧,先貼上其在 arm 架構下的實現:spa

static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) {
    // if the low bit is set, we're returning to thumb mode
    if ((uintptr_t)ra & 1) {
        // 3f 46 mov r7, r7
        // we mask off the low bit via subtraction
        // 16-bit instructions are well-aligned
        if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) {
            return true;
        }
    } else {
        // 07 70 a0 e1 mov r7, r7
        // 32-bit instructions may be only 16-bit aligned
        if (*(unaligned_uint32_t *)ra == 0xe1a07007) {
            return true;
        }
    }
    return false;
}
複製代碼

rootAutorelease2

若是以前的一系列優化沒能返回 this,最後會調用 rootAutorelease2 這個方法,其實現以下:操作系統

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
複製代碼

咱們會發現其實現至關簡單,接下來就是 autorelease 的重頭戲,autoreleasepool 到底是怎麼實現的。線程

autoreleasepool

想要完全瞭解 autoreleasepool 的實現,不是三言兩語能夠說清楚的,讓咱們接着以前的 AutoreleasePoolPage::autorelease((id)this); 來一步步分析 autoreleasepool 是如何實現的:debug

首先須要瞭解的一點是,全部 autorelease 相關的方法,最終都是經過 AutoreleasePoolPage 這個類來實現的,其 autorelease 方法實現以下:

static inline id autorelease(id obj) {
    assert(obj); // 對象不可爲 NULL
    assert(!obj->isTaggedPointer()); // 對象不可爲 tagged pointer
    id *dest __unused = autoreleaseFast(obj); 
    assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj); // 確保 dest 不存在或等於 EMPTY_POOL_PLACEHOLDER 或等於 obj
    return obj;
}
複製代碼

autoreleaseFast

autoreleaseFast 的實現以下:

static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage(); // 獲取 hotPage
    if (page && !page->full()) { // 若是 page 存在且未滿
        return page->add(obj); // 將對象添加到 page 中
    } else if (page) { // 若是 page 存在,但已經滿了。
        return autoreleaseFullPage(obj, page); // 調用 autoreleaseFullPage 方法
    } else { // 若是 page 爲 NULL
        return autoreleaseNoPage(obj); // 調用 autoreleaseNoPage 方法
    }
}
複製代碼

到這裏咱們能夠大體瞭解到,所謂的 autoreleasepool 實際上是由一個或多個 AutoreleasePoolPage 對象組成的,這些對象有容量限制,讓咱們繼續拆解這個方法。

hotPage

hotPage 方法是用來獲取當前可用 page 的,其實現以下:

static inline AutoreleasePoolPage *hotPage() {
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key); // 經過 tls 查詢可用 AutoreleasePoolPage 對象
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; // 若是查詢結果爲 EMPTY_POOL_PLACEHOLDER,返回 nil
    if (result) result->fastcheck(); // fastcheck
    return result;
}
複製代碼

這裏的 key 定義以下:

// Thread keys reserved by libc for our use.
#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
# define SUPPORT_DIRECT_THREAD_KEYS 1
# define TLS_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
# define SYNC_DATA_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
# define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
# define AUTORELEASE_POOL_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
# define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
# define SUPPORT_DIRECT_THREAD_KEYS 0
#endif

static pthread_key_t const key = AUTORELEASE_POOL_KEY;
複製代碼

fastcheck 方法實現以下:

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
}
複製代碼

通常會作一些線程之類的檢查,最終都會調用到 busted 方法,也就是實際作檢查的地方,

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());
}
複製代碼

根據 die 參數不一樣會決定是 _objc_fatal 或 _objc_inform。

full

full 方法是用來檢查 AutoreleasePoolPage 對象是否已經滿了的方法,其實現以下:

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

bool full() { 
    return next == end();
}
複製代碼

看到此處,咱們已經能夠判定 AutoreleasePoolPage 對象的數據類型是鏈表類型了。

SIZE 的定義以下:

#define I386_PGBYTES 4096
#define PAGE_SIZE I386_PGBYTES
#define PAGE_MAX_SIZE PAGE_SIZE

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
複製代碼

add

id *add(id obj) {
    assert(!full()); // 確保當前狀態不滿
    unprotect(); // 設置爲讀/寫狀態
    id *ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
    protect(); // 設置爲只讀狀態
    return ret;
}
複製代碼

protect 和 unprotect 方法實現以下:

inline void protect() {
#if PROTECT_AUTORELEASEPOOL
    mprotect(this, SIZE, PROT_READ);
    check();
#endif
}

inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
    check();
    mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
}
複製代碼

主要是使用了 mprotect 函數,mprotect 函數的原型以下:

int mprotect(const void *addr, size_t len, int prot);
複製代碼

其中 addr 是待保護的內存首地址,必須按頁對齊;len 是待保護內存的大小,必須是頁的整數倍,prot 表明模式,可能的取值有 PROT_READ(表示可讀)、PROT_WRITE(可寫)等。

不一樣體系結構和操做系統,一頁的大小不盡相同。如何得到頁大小呢?經過 PAGE_SIZE 宏或者 getpagesize() 系統調用便可。

autoreleaseFullPage

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; // 若是 page->child 存在,則給 page 賦值 page->child
        else page = new AutoreleasePoolPage(page); // 不然就初始化一個新的 page。
    } while (page->full());

    setHotPage(page); // 設置 page 爲當前 hotPage
    return page->add(obj); // 添加 obj 進入 page
}
複製代碼

AutoreleasePoolPage 的構造函數以下所示:

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();
}
複製代碼

經過初始化方法咱們能夠看到,AutoreleasePoolPage 對象不只有 child,還有 parent,由此能夠判定,其爲雙向鏈表。

setHotPage 實現以下:

static inline void setHotPage(AutoreleasePoolPage *page) {
    if (page) page->fastcheck();
    tls_set_direct(key, (void *)page);
}
複製代碼

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;
    if (haveEmptyPoolPlaceholder()) { // 若是當前有 poolPlaceholder
        // 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);
}
複製代碼

haveEmptyPoolPlaceholder 方法實現以下:

# define EMPTY_POOL_PLACEHOLDER ((id*)1)

static inline bool haveEmptyPoolPlaceholder() {
    id *tls = (id *)tls_get_direct(key);
    return (tls == EMPTY_POOL_PLACEHOLDER);
}
複製代碼

setEmptyPoolPlaceholder 方法實現以下:

static inline id* setEmptyPoolPlaceholder() {
    assert(tls_get_direct(key) == nil);
    tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
    return EMPTY_POOL_PLACEHOLDER;
}
複製代碼
相關文章
相關標籤/搜索