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

自動釋放池

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

Clang 分析

創(chuàng)建一個空工程,切換到 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);
}

通過終端命令 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 文件,打開 main.cpp 文件,查看 main 函數(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 是一個結(jié)構(gòu)體,有 構(gòu)造函數(shù) + 析構(gòu)函數(shù),結(jié)構(gòu)體定義的對象在作用域結(jié)束后,會自動調(diào)用析構(gòu)函數(shù)。本質(zhì)也是一個對象

@autoreleasepool {}
//等價于
{__AtAutoreleasePool __autoreleasepool; }

匯編分析

main 函數(shù)中打個斷點,運行程序,開啟匯編模式

通過調(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. 線程的自動釋放池是指針的堆棧。
    1. 每個指針都是要釋放的對象(或者是 POOL_BOUNDARY,POOL_BOUNDARY 是自動釋放池的邊界)。
    1. 一個自動釋放池的標(biāo)示是指向該池的 POOL_BOUNDARY 的指針。 當(dāng)自動釋放池出棧,將釋放比哨兵更熱的每個對象。
    1. 堆棧被分為一個雙向鏈接的頁面列表(page)。 根據(jù)需要添加和刪除頁面。
    1. 線程本地存儲指向熱頁面,該頁面存儲新自動釋放的對象。

自動釋放池是什么時候創(chuàng)建的?對象是如何加入自動釋放池的?哪些對象才會加入自動釋放池?帶著這些疑問,我們來一步步探索自動釋放池的底層原理

AutoreleasePoolPage

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

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

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

從上面的源碼中我們看到,兩個方法走的分別是 AutoreleasePoolPagepushpop 實現(xià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(){...}
    
    ...

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

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

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

    ...

    // 獲取當(dāng)前操作頁
    static inline AutoreleasePoolPage *hotPage() {...}

    // 設(shè)置當(dāng)前操作頁
    static inline void setHotPage(AutoreleasePoolPage *page) {...}

    // 獲取coldPage
    static inline AutoreleasePoolPage *coldPage() {...}

    // 快速釋放
    static inline id *autoreleaseFast(id obj) {...}

    // 添加自動釋放對象,當(dāng)頁滿的時候調(diào)用這個方法
    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}

    // 添加自動釋放對象,當(dāng)沒頁的時候使用這個方
    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj) {...}

    // 創(chuàng)建新的page
    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj) {...}

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


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

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

    ...
    
#undef POOL_BOUNDARY
};

AutoreleasePoolPage 定義發(fā)現(xiàn),它是一個 page,同時也是一個 對象,這個頁的大小為 4096 字節(jié)。AutoreleasePoolPage 繼承自 AutoreleasePoolPageData,那么它的結(jié)構(gòu)是什么樣呢?如下

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    // 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整
    magic_t const magic;
    // 指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin()
    __unsafe_unretained id *next;
    // 指向當(dāng)前線程
    pthread_t const thread;
    // 指向父節(jié)點,第一個結(jié)點的 parent 值為 nil
    AutoreleasePoolPage * const parent;
    // 指向子節(jié)點,最后一個結(jié)點的 child 值為 nil
    AutoreleasePoolPage *child;
    // 表示深度,從 0 開始,往后遞增 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,這里也間接的證明了自動釋放池是一個 雙向鏈表的頁 結(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(對象)、parent(對象)、child(對象) 均占 8 字節(jié),共 32 字節(jié);uint32_t 兩個各占 4 字節(jié),共 8字節(jié)。

objc_autoreleasePoolPush

通過上面的分析,我們進入 AutoreleasePoolPagepush 實現(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)建過 pool
    1. 沒有,創(chuàng)建一個新的 page
    1. 壓棧一個 POOL_BOUNDARY(哨兵)

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

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

static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
    // 獲取當(dāng)前操作頁
    AutoreleasePoolPage *page = hotPage();
    // 壓棧對象
    if (page) return autoreleaseFullPage(obj, page);
    // 創(chuàng)建頁
    else return autoreleaseNoPage(obj);
}

// 獲取當(dāng)前操作頁(hotPage)
static inline AutoreleasePoolPage *hotPage()
{
    AutoreleasePoolPage *result = (AutoreleasePoolPage *)
        tls_get_direct(key);
    // 如果是個空池,返回 nil
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
    //返回當(dāng)前線程的自動釋放池
    if (result) result->fastcheck();
    return result;
}
  • 如果存在 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);

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

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

通過操作 child 對象,將當(dāng)前頁的 child 指向新建頁面,建立雙向鏈表連接

  • 如果不存在 hotpage,通過 autoreleaseNoPage 創(chuà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;
    // 判斷是否是空占位符,如果是,則壓棧哨兵標(biāo)識符置為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;
    }
    // 如果對象不是哨兵對象,而且沒有 Pool,則報錯
    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;
    }
    // 如果對象是哨兵對象,并且沒有申請自動釋放池內(nèi)存,則設(shè)置一個空占位符
    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);
    // 設(shè)置 page 為當(dāng)前操作頁
    setHotPage(page);
    
    // Push a boundary on behalf of the previously-placeholder'd pool.
    // 壓棧哨兵的標(biāo)識符為YES,則壓棧哨兵對象
    if (pushExtraBoundary) {
        // 壓棧哨兵對象
        page->add(POOL_BOUNDARY);
    }
    
    //壓棧對象
    // Push the requested object or pool.
    return page->add(obj);
}

當(dāng)前頁面不存在或者子頁面不存在時,通過 AutoreleasePoolPage 的構(gòu)造方法創(chuàng)建新的 AutoreleasePoolPage,而它的構(gòu)造方法實現(xiàn)是通過 AutoreleasePoolPageData 的初始化方法來的

/**AutoreleasePoolPage 的構(gòu)造方法*/
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(), // 開始存儲的位置
                            objc_thread_self(), // 傳的是當(dāng)前線程,當(dāng)前線程時通過 tls 獲取的
                            newParent, 
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        //this 表示新建頁面,將當(dāng)前頁面的子節(jié)點賦值為新建頁面
        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() 表示壓棧的位置(即下一個釋放對象的壓棧地址)。當(dāng)前頁面首地址+56(56 是 AutoreleasePoolPageData 的內(nèi)存大?。?/li>
id * begin() {
    // sizeof(*this) = 56
    return (id *) ((uint8_t *)this+sizeof(*this));
}
  • objc_thread_self() 表示當(dāng)前線程,而當(dāng)前線程時通過 tls 獲取的
__attribute__((const))
static inline pthread_t objc_thread_self()
{
    // 通過tls獲取當(dāng)前線程
    return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
  • newParent 表示的是 AutoreleasePoolPageData 的父節(jié)點

  • newParent ? 1+newParent->depth : 0 表示通過父節(jié)點的深度計算 depth

  • newParent ? newParent->hiwat : 0 表示通過父節(jié)點的最大入棧個數(shù)計算 hiwat

  • add 方法

添加釋放對象,其底層是實現(xiàn)是通過 next 指針存儲釋放對象,并將 next 指針遞增,表示下一個釋放對象存儲的位置。從這里可以看出頁是通過棧結(jié)構(gòu)存儲

id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    
    // 傳入對象存儲的位置
    id *ret = next;  // faster than `return next-1` because of aliasing
    // 將obj壓棧到next指針位置,然后next進行++,即下一個對象存儲的位置
    *next++ = obj;
    protect();
    return ret;
}

2. autoreleaseFast(壓棧對象)

源碼實現(xiàn)如下

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

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

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

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //循環(huán)創(chuàng)建對象,并加入自動釋放池
        for (int i = 0; i < 5; i++) {
             NSObject *objc = [[NSObject alloc] autorelease];
        }
        //調(diào)用
        _objc_autoreleasePoolPrint();
    }
}

運行項目,打印結(jié)果如下

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

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

從打印結(jié)果可以看到,第一頁已經(jīng)存滿了,存儲了 504 個 需要釋放的對象,第二頁存儲了一個對象,如果我們將 for 循環(huán)次數(shù)改為 1010 個呢?

通過運行發(fā)現(xiàn),第一頁存儲 504 個,第二頁存儲 505 個,第三頁存儲 1 個

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

同樣這個結(jié)論可以通過 AutoreleasePoolPageSIZE 來驗證,定義中 PAGE_MIN_SIZE 大小為 4096 字節(jié),在其構(gòu)造函數(shù)中對象的壓棧位置 begin() 是從 首地址+56 字節(jié)開始的,所以在一個 page 中實際可以存儲 4096-56 = 4040 字節(jié),轉(zhuǎn)換成對象 4040/8 = 505 個,因為第一頁有哨兵對象,最多存儲 504 個

objc_autoreleasePoolPop

objc-781 源碼中我們看到 objc_autoreleasePoolPop 實現(xiàn)源碼中有個參數(shù),這個參數(shù)在 clang 分析中可以找到,在 objc_autoreleasePoolPush() 返回一個 atautoreleasepoolobj (哨兵對象),即 ctxt。其目的是為了避免出?;靵y。

  • 進入 pop 源碼,實現(xiàn)如下
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.
            // 如果當(dāng)前操作頁不存在,清空占位符
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        // 如果當(dāng)前操作頁存在,將當(dāng)前操作頁設(shè)置為 coldPage,將 token 設(shè)置為 coldPage 的起始位置
        page = coldPage();
        token = page->begin();
    } else {
        // 獲取 token 所在的頁
        page = pageForPointer(token);
    }

    stop = (id *)token;
    // 判斷是否是哨兵對象
    if (*stop != POOL_BOUNDARY) {
        if (stop == page->begin()  &&  !page->parent) {
            // 如果是當(dāng)前頁的第一個位置,且沒有父節(jié)點,什么也不做
            // 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);
    }

    // 出棧頁
    return popPage<false>(token, page, stop);
}
    1. 空白頁處理/根據(jù)token獲取page
    1. 容錯處理
    1. popPage 出棧

進入 popPage 的源碼。通過 releaseUntil 出棧當(dāng)前頁 stop 位置之前的所有對象,即向棧中的對象發(fā)送 release 消息,直到遇到傳入的哨兵對象。

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

    // 當(dāng)前操作頁面出棧
    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)前頁面的父節(jié)點
        AutoreleasePoolPage *parent = page->parent;
        // 將當(dāng)前頁面 kill
        page->kill();
        // 設(shè)置父頁面為當(dāng)前操作頁
        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();
        }
    }
}

進入 releaseUntil 實現(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
    // 判斷下一個對象是否等于 stop,不等于,一直 while 循環(huán)
    while (this->next != stop) {
        // Restart from hotPage() every time, in case -release
        // autoreleased more objects
        // 獲取當(dāng)前操作頁
        AutoreleasePoolPage *page = hotPage();

        // fixme I think this `while` can be `if`, but I can't prove it
        // 如果當(dāng)前操作頁是空的
        while (page->empty()) {
            // 將 page 賦值為父節(jié)點
            page = page->parent;
            // 將父節(jié)點設(shè)置為當(dāng)前操作頁
            setHotPage(page);
        }

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

下面我們進入 kill 的源碼,實現(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;
    // 獲取最后一個 page
    while (page->child) page = page->child;

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

主要是銷毀當(dāng)前頁,將當(dāng)前頁賦值為父節(jié)點頁,并將父節(jié)點頁的 child 對象指針置為 nil

autorelease 源碼分析

上面我們知道了 autoreleasepool 的底層原理,下面我們來看下 autorelease 的底層實現(xiàn),如下,我們打開匯編看下 autorelease 的底層調(diào)用(因為 Xcode 默認(rèn)是 ARC 模式,不能調(diào)用 autorelease,需要開啟 MRC 模式)

打開 objc781 源碼,我們進入 objc_autorelease 的源碼,如下

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

繼續(xù)往下走

inline id 
objc_object::autorelease()
{
    ASSERT(!isTaggedPointer());
    // 判斷是否是自定義類
    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;
    }

無論是壓棧哨兵對象,還是普通對象,都會來到 autoreleaseFast方法,只是區(qū)別標(biāo)識不同而以

總結(jié)

通過以上的分析,針對自動釋放池的 push 以及 pop,做個總結(jié)

objc_autoreleasePoolPush

    1. 判斷有沒有 pool,即只有空占位符(存儲在tls中)時,創(chuàng)建頁,并壓棧哨兵對象
    1. 壓棧對象,通過 page->add(obj) 方法,將 next 指針遞增
    1. 當(dāng)頁面滿了,設(shè)置當(dāng)前操作頁的 child 為新建頁,并設(shè)置新建頁為當(dāng)前操作頁,壓棧對象

push 流程圖

objc_autoreleasePoolPop

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

pop 流程圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容