AutoreleasePool的實現

NeroXie發表於2019-02-27

原文連結AutoreleasePool的實現

在MRC中,呼叫[obj autorelease]來延遲記憶體的釋放;在ARC下,物件呼叫autorelease方法,就會被自動新增到最近的自動釋放池,只有當自動釋放池被銷燬的時候,才會執行release方法,進行釋放。真實結果到底是什麼,等看完原始碼後我們就會知道了。

AutoreleasePool

main.m中有一段程式碼:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
複製程式碼

轉換成C++程式碼:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}
複製程式碼

@autoreleasepool變成__AtAutoreleasePool __autoreleasepool__AtAutoreleasePool結構體定義如下:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
複製程式碼

它提供了兩個方法:objc_autoreleasePoolPush()objc_autoreleasePoolPop。這兩個方法的定義在NSObject.mm檔案中,分別是:

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

void
objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}
複製程式碼

所以,autoreleasepool的自動釋放的核心就是AutoreleasePoolPage類

AutoreleasePoolPage

NSObject.mm檔案中定義了AutoreleasePoolPage,這裡我們只顯示這個類比較重要的屬性,如下:

class AutoreleasePoolPage {
    static size_t const SIZE = PAGE_MAX_SIZE;//SIZE是AutoreleasePoolPage的大小,4096個位元組
    magic_t const magic; //autoreleasepool完整性校驗
    id *next;//AutoreleasePoolPage單個節點是一個連結串列,next指向棧頂的最新的autorelease物件的下一個位置
    pthread_t const thread;//當前所在的執行緒
    AutoreleasePoolPage * const parent;//指標
    AutoreleasePoolPage *child;//指標
    uint32_t const depth;//深度
    uint32_t hiwat;
}
複製程式碼

通過原始碼我們可以知道:

  • AutoreleasePool並沒有特定的記憶體結構,它是通過以AutoreleasePoolPage為節點的雙向連結串列。
  • 每一個AutoreleasePoolPage節點是一個堆疊結,且大小為4096個位元組。
  • 一個AutoreleasePoolPage節點對應著一個執行緒,屬於一一對應關係。

AutoreleasePool結構如圖所示:

AutoreleasePoolPage連結串列

接著我們看一下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();
    }
    
    //相關操作方法
    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    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;
    }
複製程式碼
  • begin() 表示了一個AutoreleasePoolPage節點開始存autorelease物件的位置。
  • end() 一個AutoreleasePoolPage節點最大的位置
  • empty() 如果next指向beigin()說明為空
  • full() 如果next指向end)說明滿了
  • id *add(id obj) 新增一個autorelease物件,next指向下一個存物件的地址。

所以一個空的AutoreleasePoolPage的結構如下:

AutoreleasePoolPage

AutoreleasePoolPage::push()

push程式碼如下:

    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;
    }
複製程式碼

push執行的時候首先會進行判斷,如果是需要每個pool都生成一個新page,即DebugPoolAllocationYES,則執行autoreleaseNewPage方法,否則執行autoreleaseFast方法。

autoreleaseNewPage

autoreleaseNewPage分為兩種情況:

  1. 當前存在page執行autoreleaseFullPage方法;
  2. 當前不存在pageautoreleaseNoPage方法。

autoreleaseFast

autoreleaseFast分為三種情況:

  1. 存在page且未滿,通過add()方法進行新增;
  2. 當前page已滿執行autoreleaseFullPage方法;
  3. 當前不存在page執行autoreleaseNoPage方法。

hotPage

前面講到的page其實就是hotPage,通過AutoreleasePoolPage *page = hotPage();獲取。

    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;
    }
複製程式碼

通過上面的程式碼我們知道當前頁是存在TLS(執行緒私有資料)裡面的。所以說第一次呼叫push的時候,沒有page自然連hotPage也沒有

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;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }
複製程式碼

autoreleaseFullPage會從傳入的page開始遍歷整個雙向連結串列,如果page滿了,就看它的child節點,直到查詢到一個未滿的AutoreleasePoolPage。接著使用AutoreleasePoolPage建構函式傳入parent建立一個新的AutoreleasePoolPage的節點(此時跳出了while迴圈)。

在查詢到一個可以使用的AutoreleasePoolPage之後,會將該頁面標記成hotPage,然後調動add()方法新增物件。

autoreleaseNoPage

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        //"no page"意味著沒有沒有池子被push或者說push了一個空的池子
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {//push了一個空的池子
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            _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) {
            //沒有池子被push
            return setEmptyPoolPlaceholder();
        }

        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            //push了一個空的池子,新增哨兵物件
            page->add(POOL_BOUNDARY);
        }
    
        return page->add(obj);
    }
    
    //haveEmptyPoolPlaceholder的本質
        static inline bool haveEmptyPoolPlaceholder()
    {
        id *tls = (id *)tls_get_direct(key);
        return (tls == EMPTY_POOL_PLACEHOLDER);
    }
複製程式碼

從上面的程式碼我們可以知道,既然當前記憶體中不存在AutoreleasePoolPage,就要從頭開始構建這個自動釋放池的雙向連結串列,也就是說,新的AutoreleasePoolPage是沒有parent指標的。

初始化之後,將當前頁標記為hotPage,然後會先向這個page中新增一個POOL_BOUNDARY的標記,來確保在pop呼叫的時候,不會出現異常。

最後,將obj新增到自動釋放池中。

autorelease方法

接著看一下當物件呼叫autorelase方法發生了什麼。

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id 
objc_object::rootAutorelease()
{
    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;
}
複製程式碼

從上面的原始碼我們看到,物件呼叫autorelase方法,最後會變成AutoreleasePoolPageautorelease函式。AutoreleasePoolPageautorelease的本質就是呼叫autoreleaseFast(obj)函式。只不過push操作插入的是一個POOL_BOUNDARY ,而autorelease操作插入的是一個具體的autoreleased物件即AutoreleasePoolPage入棧操作。

當然這麼說並不嚴謹,因為我們需要考慮是否是Tagged Pointer和是否進行優化的情況(prepareOptimizedReturn這個後面也會提到),如果不滿足這兩個條件才會進入快取池。

所以push的流程是:

AutoreleasePoolPush流程

AutoreleasePoolPage::pop(ctxt)

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        //第一種情況:autoreleasepool首次push的時候返回的,也就是最頂層的page執行pop會執行這一部分
        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);
        
        //https://stackoverflow.com/questions/24952549/does-nsthread-create-autoreleasepool-automatically-now
        //第二種情況:在非ARC的情況下,在新建立的執行緒中不使用autoreleasepool,直接呼叫autorelease方法時會出現這個情況。此時沒有pool,直接進行autorelease。
        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 (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();
            }
        }
    }
複製程式碼

這裡我們主要分析下第三種情況。

releaseUntil

void releaseUntil(id *stop) {
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();

        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }

    setHotPage(this);
}
複製程式碼

從next指標開始,一個一個向前呼叫objc_release,直到碰到push時壓入的pool為止。

所以autoreleasePool的執行過程應該是:

pool1 = push()
...
    pool2 = push()
    ...
        pool3 = push()
        ...
        pop(pool3)
    ...
    pop(pool2)
...
pop(pool1)
複製程式碼

每次pop,實際上都會把最近一次push之後新增進去的物件全部release掉。

AutoreleasePool、Runloop、執行緒之間的關係

蘋果的文件中提到:

Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.

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.

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

我們可以知道:

  1. 每一個執行緒,包括主執行緒,都會擁有一個專屬的runloop,並且會在有需要的時候自動建立。

  2. 主執行緒在runloop開始之前會自動建立一個autoreleasePool,並在結束時pop。那其他的執行緒呢?這裡我做了一個實驗,程式碼如下:

  3. 每一個執行緒都會維護自己的autoreleasePool堆疊,也就是說每一個autoreleasePool對應一個執行緒。

進入AutoreleasePool的時機

那什麼樣的物件會進入autoreleasePool呢?

測試

測試1.1程式碼

NSMutableArray *arr = [NSMutableArray new];
NSLog(@"%lu", _objc_rootRetainCount(arr));
_objc_autoreleasePoolPrint();
複製程式碼

結果

2019-01-22 15:50:45.129263+0800 AutoreleasePool[31529:22121744] 1
objc[31529]: ##############
objc[31529]: AUTORELEASE POOLS for thread 0x1176345c0
objc[31529]: 2 releases pending.
objc[31529]: [0x7f96e0802000]  ................  PAGE  (hot) (cold)
objc[31529]: [0x7f96e0802038]    0x600003ac8f00  __NSArrayI
objc[31529]: [0x7f96e0802040]    0x600000ceef80  __NSSetI
objc[31529]: ##############
複製程式碼

測試1.2程式碼

@autoreleasepool{
    NSMutableArray *arr = [NSMutableArray new];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
    _objc_autoreleasePoolPrint();
}
複製程式碼

結果

2019-01-22 15:53:28.818873+0800 AutoreleasePool[31568:22134125] 1
objc[31568]: ##############
objc[31568]: AUTORELEASE POOLS for thread 0x10d20e5c0
objc[31568]: 3 releases pending.
objc[31568]: [0x7fcf66002000]  ................  PAGE  (hot) (cold)
objc[31568]: [0x7fcf66002038]    0x600000129200  __NSArrayI
objc[31568]: [0x7fcf66002040]    0x60000370b020  __NSSetI
objc[31568]: [0x7fcf66002048]  ################  POOL 0x7fcf66002048
objc[31568]: ##############
複製程式碼

測試1.3程式碼

{
    NSMutableArray *arr = [NSMutableArray new];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
    _objc_autoreleasePoolPrint();
}

@autoreleasepool{
    NSMutableArray *arr = [NSMutableArray new];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
     _objc_autoreleasePoolPrint();
}
複製程式碼

結果

2019-01-22 15:55:21.271452+0800 AutoreleasePool[31596:22141965] 1
objc[31596]: ##############
objc[31596]: AUTORELEASE POOLS for thread 0x1166f15c0
objc[31596]: 2 releases pending.
objc[31596]: [0x7fdcaf002000]  ................  PAGE  (hot) (cold)
objc[31596]: [0x7fdcaf002038]    0x600003e6a500  __NSArrayI
objc[31596]: [0x7fdcaf002040]    0x600000849db0  __NSSetI
objc[31596]: ##############
2019-01-22 15:55:21.272353+0800 AutoreleasePool[31596:22141965] 1
objc[31596]: ##############
objc[31596]: AUTORELEASE POOLS for thread 0x1166f15c0
objc[31596]: 3 releases pending.
objc[31596]: [0x7fdcaf002000]  ................  PAGE  (hot) (cold)
objc[31596]: [0x7fdcaf002038]    0x600003e6a500  __NSArrayI
objc[31596]: [0x7fdcaf002040]    0x600000849db0  __NSSetI
objc[31596]: [0x7fdcaf002048]  ################  POOL 0x7fdcaf002048
objc[31596]: ##############
複製程式碼

測試2.1程式碼

 NSMutableArray *arr = [NSMutableArray array];
 NSLog(@"%lu", _objc_rootRetainCount(arr));
 _objc_autoreleasePoolPrint();
複製程式碼

結果

2019-01-22 15:57:02.360860+0800 AutoreleasePool[31615:22149043] 2
objc[31615]: ##############
objc[31615]: AUTORELEASE POOLS for thread 0x1111eb5c0
objc[31615]: 3 releases pending.
objc[31615]: [0x7fbf00002000]  ................  PAGE  (hot) (cold)
objc[31615]: [0x7fbf00002038]    0x600003f15c00  __NSArrayI
objc[31615]: [0x7fbf00002040]    0x6000009705f0  __NSSetI
objc[31615]: [0x7fbf00002048]    0x600002404f30  __NSArrayM
objc[31615]: ##############
複製程式碼

測試2.2程式碼

@autoreleasepool {
    NSMutableArray *arr = [NSMutableArray array];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
    _objc_autoreleasePoolPrint();
}
複製程式碼

結果

2019-01-22 15:58:29.932693+0800 AutoreleasePool[31634:22153810] 2
objc[31634]: ##############
objc[31634]: AUTORELEASE POOLS for thread 0x115aac5c0
objc[31634]: 4 releases pending.
objc[31634]: [0x7f867c002000]  ................  PAGE  (hot) (cold)
objc[31634]: [0x7f867c002038]    0x600000b00080  __NSArrayI
objc[31634]: [0x7f867c002040]    0x600003d64190  __NSSetI
objc[31634]: [0x7f867c002048]  ################  POOL 0x7f867c002048
objc[31634]: [0x7f867c002050]    0x60000100ff30  __NSArrayM
objc[31634]: ##############
複製程式碼

測試2.3程式碼

{
    NSMutableArray *arr = [NSMutableArray array];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
    _objc_autoreleasePoolPrint();
}

@autoreleasepool {
    NSMutableArray *arr = [NSMutableArray array];
    NSLog(@"%lu", _objc_rootRetainCount(arr));
     _objc_autoreleasePoolPrint();
}
複製程式碼

結果

2019-01-22 16:01:11.925690+0800 AutoreleasePool[31670:22164284] 2
objc[31670]: ##############
objc[31670]: AUTORELEASE POOLS for thread 0x11bffa5c0
objc[31670]: 3 releases pending.
objc[31670]: [0x7ff965802000]  ................  PAGE  (hot) (cold)
objc[31670]: [0x7ff965802038]    0x600001c1eb00  __NSArrayI
objc[31670]: [0x7ff965802040]    0x600002a47200  __NSSetI
objc[31670]: [0x7ff965802048]    0x600000712490  __NSArrayM
objc[31670]: ##############
2019-01-22 16:01:11.926577+0800 AutoreleasePool[31670:22164284] 1
objc[31670]: ##############
objc[31670]: AUTORELEASE POOLS for thread 0x11bffa5c0
objc[31670]: 4 releases pending.
objc[31670]: [0x7ff965802000]  ................  PAGE  (hot) (cold)
objc[31670]: [0x7ff965802038]    0x600001c1eb00  __NSArrayI
objc[31670]: [0x7ff965802040]    0x600002a47200  __NSSetI
objc[31670]: [0x7ff965802048]    0x600000712490  __NSArrayM
objc[31670]: [0x7ff965802050]  ################  POOL 0x7ff965802050
objc[31670]: ##############****
複製程式碼

從上面的程式碼我們可以知道,使用newalloc這樣的方法建立的物件例項是不會進入autoreleasePool的,但是使用簡便方法建立的物件如[NSMutableArray array]是會進入自動快取池的

但是在測試2.3上,我們可以看到,只有一個array進入了自動快取池,另外一個沒有進入。看一下它的方法呼叫棧:

1548145578479

在《Objective-C高階程式設計》第66-67頁提到了最優化程式執行。通過objc_retainAutoreleasedReturnValueobjc_retainAutoreleaseReturnValue函式的協作,可以不將物件註冊到autoreleasePool中而直接傳遞,這一過程達到最優化。

objc_retainAutoreleasedReturnValue

objc_retainAutoreleasedReturnValue的實現如下:

// Accept a value returned through a +0 autoreleasing convention for use at +1.
id
objc_retainAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition 
acceptOptimizedReturn()
{
    ReturnDisposition disposition = getReturnDisposition();
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

static ALWAYS_INLINE ReturnDisposition 
getReturnDisposition()
{
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}
複製程式碼

通過上面的程式碼我們可以知道objc_retainAutoreleasedReturnValue會嘗試接收一個被優化的結果,如何是ReturnAtPlus1YES,返回物件本身,否則執行objc_retain(obj

這個被優化的結果是線上程私有資料TLS中的,我們可以理解為一個優化位。當優化位返回YES的時候,直接返回物件本身,否則執行retain。

objc_retainAutoreleaseReturnValue

objc_retainAutoreleaseReturnValue的實現如下:

// Prepare a value at +0 for return through a +0 autoreleasing convention.
id 
objc_retainAutoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus0)) return obj;

    // not objc_autoreleaseReturnValue(objc_retain(obj)) 
    // because we do not need another optimization attempt
    return objc_retainAutoreleaseAndReturn(obj);
}

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

    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

static ALWAYS_INLINE bool 
callerAcceptsOptimizedReturn(const void * const ra0)
{
    const uint8_t *ra1 = (const uint8_t *)ra0;
    const unaligned_uint16_t *ra2;
    const unaligned_uint32_t *ra4 = (const unaligned_uint32_t *)ra1;
    const void **sym;

#define PREFER_GOTPCREL 0
#if PREFER_GOTPCREL
    // 48 89 c7    movq  %rax,%rdi
    // ff 15       callq *symbol@GOTPCREL(%rip)
    if (*ra4 != 0xffc78948) {
        return false;
    }
    if (ra1[4] != 0x15) {
        return false;
    }
    ra1 += 3;
#else
    // 48 89 c7    movq  %rax,%rdi
    // e8          callq symbol
    if (*ra4 != 0xe8c78948) {
        return false;
    }
    ra1 += (long)*(const unaligned_int32_t *)(ra1 + 4) + 8l;
    ra2 = (const unaligned_uint16_t *)ra1;
    // ff 25       jmpq *symbol@DYLDMAGIC(%rip)
    if (*ra2 != 0x25ff) {
        return false;
    }
#endif
    ra1 += 6l + (long)*(const unaligned_int32_t *)(ra1 + 2);
    sym = (const void **)ra1;
    if (*sym != objc_retainAutoreleasedReturnValue  &&  
        *sym != objc_unsafeClaimAutoreleasedReturnValue) 
    {
        return false;
    }

    return true;
}

// Same as objc_retainAutorelease but suitable for tail-calling 
// if you do not want to push a frame before this point.
__attribute__((noinline))
static id 
objc_retainAutoreleaseAndReturn(id obj)
{
    return objc_retainAutorelease(obj);
}

id
objc_retainAutorelease(id obj)
{
    return objc_autorelease(objc_retain(obj));
}
複製程式碼

這裡主要涉及到callerAcceptsOptimizedReturn,這個函式意思不是很理解,但是裡面涉及到了objc_retainAutoreleasedReturnValue,猜測可能是程式檢測在返回值之後是否緊接著呼叫了objc_retainAutoreleasedReturnValue,如果是,就知道了外部是ARC環境走優化路線,反之就走沒被優化的邏輯。

所以個人認為,使用newalloc這樣的方法建立的物件例項是不會進入autoreleasePool的,但是使用簡便方法建立的物件,程式會進行優化後,再決定是否進入自動快取池

另外關於main函式中的@autoreleasepool的作用是什麼?簡單的說就是讓那些進入自動快取池的物件有個地方被釋放。

總結

  1. 在APP中,整個主執行緒是執行在一個自動釋放池中的。

  2. main函式中的自動釋放池的作用:這個池塊給出了一個pop點來顯式的告訴我們這裡有一個釋放點,如果你的main在初始化的過程中有別的內容可以放在這裡。

  3. 使用@autoreleasepool標記,呼叫push()方法。

  4. 沒有hotpage,呼叫autoreleaseNoPage(),設定EMPTY_POOL_PLACEHOLDER

  5. 因為設定了EMPTY_POOL_PLACEHOLDER,所以會設定本頁為hotpage,新增邊界標記POOL_BOUNDARY,最後新增obj。

  6. 繼續有物件呼叫autorelease,此時已經有了page,呼叫page->add(obj)

  7. 如果page滿了,呼叫autoreleaseFullPage()建立新page,重複第6點。

  8. 到達autoreleasePool邊界,呼叫pop方法,通常情況下會釋放掉POOL_BOUNDARY之後的所有物件

相關文章