帶你瞭解autroreleasePool的底層實現原理

一.探索前需知

1.1 什麼是ARC與MRC?

相信你們早已對這個問題爛熟於心,但仍是帶你們一塊兒過一下.bash

在MRC時代,系統斷定一個對象是否銷燬是根據這個對象的引用計數器來判斷的.其中每一個對象被建立時引用計數都爲1,每當對象被其餘指針引用時,須要手動使用[obj retain];讓該對象引用計數+1,當指針變量不在使用這個對象的時候,須要手動釋放release這個對象。 讓其的引用計數-1,當一個對象的引用計數爲0的時候,系統就會銷燬這個對象.總的來講在MRC模式下必須遵循誰建立,誰釋放,誰引用,誰管理app

若是在MRC下使用ARC:
在Build Phases的Compile Sources中選擇須要使用MRC方式的.m文件,而後雙擊該文件在彈出的會話框中輸入 -fobjc-arcless

ARC自動內存管理:
WWDC2011和iOS5所引入自動管理機制——自動引用計數(ARC),它不是垃圾回收機制而是編譯器的一種特性。ARC管理機制與MRC手動機制差很少,只是再也不須要手動調用retain、release、autorelease;當你使用ARC時,編譯器會在在適當位置插入release和autorelease;ARC時代引入了strong強引用來帶代替retain,引入了weak弱引用.總結來講ARC是LLVM和Runtime配合的結果函數

在ARC下使用MRC方法:
在ARC工程中若是要使用MRC的須要在工程的Build Phases的Compile Sources中選擇須要使用MRC方式的.m文件,而後雙擊該文件在彈出的會話框中輸入 -fno-objc-arc源碼分析

1.2 autoreleasePool自動釋放池

自動釋放池始於MRC時代,主要是用於 自動 對 釋放池內 對象 進行引用計數-1的操做,即自動執行release方法,在MRC中使用autoreleasepool必須在代碼塊內部手動爲對象調用autorelease把對象加入到的自動釋放池,系統會自動在代碼塊結束後,對加入自動釋放池中的對象發送一個release消息.無需手動調用release.ui

二 .autoreleasePool的初探

2.1 autoreleasePool的建立

咱們在建立工程的時候默認建立的.m文件main函數有個autoreleasePool的建立,而咱們本身的代碼中也能夠建立autoreleasePool 對象:this

main函數裏的:spa

#import <UIKit/UIKit.h>

#import "AppDelegate.h"

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

 本身代碼中的:線程

- (void)testConst{
    
    @autoreleasepool {
        
        for (int i = 0; i<1000; i++) {
            NSString *str1 = [NSString stringWithFormat:@"%ld",i];
        }
    }
}
複製代碼

在這段代碼裏會建立大量的臨時變量(先不考慮代碼合不合理哈),就會消耗過多的內存空間,因此在開發過程當中,若是當遇到須要建立、使用大量的臨時變量時,能夠將相關的代碼放在autoreleasePool中進行,當出了@autoreleasepool {},這些臨時變量便會自動的進行釋放.debug

2.2 autoreleasePool的實現

咱們想看下autoreleasePool的底層實現該如何?

通常咱們想看系統底層實現,通常有兩個途徑:第一是LLVM源碼分析 第二個就是clang-rewrite-objc 進行底層編譯,如今咱們先用第二種方式進行探究:

將.m文件 clang-rewrite-objc -o main.cpp

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

原來__AtAutoreleasePool 是個結構體,結構體裏有:

__AtAutoreleasePool() {

     atautoreleasepoolobj = objc_autoreleasePoolPush();

}和

~__AtAutoreleasePool() {

   objc_autoreleasePoolPop(atautoreleasepoolobj);

}這兩個函數,其實__AtAutoreleasePool(){}是構造函數,而~__AtAutoreleasePool() {}這個是析構函數,其實也好理解當@autoreleasepool {}剛建立的時候底層會對atautoreleasepoolobj進行構造,當出了@autoreleasepool {} 方法區域的時候,底層會對atautoreleasepoolobj進行析構pop出去.

可是objc_autoreleasePoolPush、objc_autoreleasePoolPop 到底作了什麼事呢?咱們只有從源碼中找到答案了,請看下面的分析 autoreleasePool的進階.

三 .autoreleasePool的進階

想要了解底層,必需要有源碼,首先咱們在網上 http://www.opensource.apple.com/apsl/裏下一份objc的源碼經過一些配置導入咱們的工程.(最新的版本好像是objc4-779.1版本)

3.1  objc_autoreleasePoolPush方法實現:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
複製代碼

AutoreleasePoolPage 又是什麼東西呢?點進去看看:

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
.
.
.
}

複製代碼

原來AutoreleasePoolPage繼承於AutoreleasePoolPageData, AutoreleasePoolPageData 結構以下:

struct AutoreleasePoolPageData
{
	magic_t const magic; // 16
	__unsafe_unretained id *next; //8
	pthread_t const thread; // 8
	AutoreleasePoolPage * const parent; //8
	AutoreleasePoolPage *child; //8
	uint32_t const depth; // 4
	uint32_t hiwat; // 4

	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是個結構體,裏面有本身的一些成員變量,如:magic_t的結構體變量、next指針類型變量、depth、hiwat變量、還有parent、child指針成員變量,

其中指針類型佔8個字節,uint32_t類型佔4個字節,結構體類型取決於結構體自己的大小,好比magic_t結構體 ,裏面是:

static const uint32_t M0 = 0xA1A1A1A1;
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;
uint32_t m[4];
複製代碼

有的盆友會說到 int32_t M0、size_t M1_len 各佔4個字節,可是忽略了一點:

static const,靜態變量.靜態變量類型數據大小是放在內存裏的全局數據段裏,不在堆區,

因此magic_t結構體所佔的大小是uint32_t m[4]的大小,一個uint32_t 爲四字節,整個就是 4*4爲16個字節.

因此AutoreleasePoolPageData結構體本身變量共佔 56個字節.各個變量所佔大小已在上面標註,其中各個變量所表明的意思以下:


總結一下AutoreleasePoolPage其實就是一個雙向鏈表結構,AutoreleasePoolPage(自動釋放池頁) 用來存放 autorelease 的對象可是每一頁的大小是有限制的,假如某個AutoreleasePoolPage頁中須要存放的autorelease 的對象過多,一頁存放不完,因此它就須要指向父結點點,在指向父結點裏的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
}複製代碼

有個PAGE_MAX_SIZE,點擊進去是4096.原來每一頁AutoreleasePoolPage能夠存放4096個字節.一共4096個字節, (4096 - AutoreleasePoolPage 中本身成員變量所佔的字節)/每一個對象中所佔的字節.  (4096 - 56)/8 = 505. 好的,每一AutoreleasePoolPage能夠存放505個對象.下面把AutoreleasePoolPage 結構圖放在下面,僅供你們參考.

結構弄清楚以後,咱們繼續跟代碼:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
複製代碼

static inline void *push() 
    {
        id *dest;
        if (slowpath(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;
    }
複製代碼

程序代碼回來到else的判斷裏,  dest = autoreleaseFast(POOL_BOUNDARY);這個POOL_BOUNDARY 就至關於 AutoreleasePoolPage 裏的邊界,一些技術書籍裏也稱做是哨兵對象,這個對象頗有用的,等下就來看蘋果爲什麼這麼設計:

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

以前經過分析代碼一進入這個 @autoreleasepool {}時,就會調用objc_autoreleasePoolPush方法,接着來到了 autoreleaseFast 方法,AutoreleasePoolPage *page = hotPage();取的此時的page,由於第一次建立 page確定取不到,因此會來到autoreleaseNoPage(obj)方法,

id *autoreleaseNoPage(id obj)
    {
        ASSERT(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            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", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            return setEmptyPoolPlaceholder();
        }
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);
    }

複製代碼

一步步執行代碼發現 會來到:

// Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        return page->add(obj);複製代碼

建立個new AutoreleasePoolPage,而且setHotPage,緊接着將咱們的哨兵對象加入到AutoreleasePoolPage.也就是page->add(POOL_BOUNDARY);操做.

3.2 autorelease 方法實現

剛剛咱們在3.1的時候說到,@autoreleasepool {} 會建立個new AutoreleasePoolPage,這個AutoreleasePoolPage 會將POOL_BOUNDARY 添加進來,那麼何時會添加自動釋放的對象呢?

確定是當某個對象調用 autorelease方法 的時候,AutoreleasePoolPage 會把調用的對象加進來.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        NSObject *objc = [[NSObject alloc] autorelease];
        NSLog(@"objc = %@",objc);
}複製代碼

回來到底層objc:

objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}
複製代碼

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()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
複製代碼

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

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

終於來到這裏了,AutoreleasePoolPage *page = hotPage(); 獲取page,在3.1的時候page已經建立了,因此他回來到 if (page && !page->full()) ,看看本頁(AutoreleasePoolPage)有沒有滿,沒滿的話直接添加 page->add(obj);若是滿的話來到autoreleaseFullPage(obj, page);

id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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);
    }
複製代碼

首先來到這裏是個do-while 循環,while (page->full()); 這個方法就是由於以前的那個page滿了纔會來到這裏,因此這個條件必然知足.而後進行判斷 page是否有子節點了,若是有的話直接用子節點的page,若是沒有的話那隻能新建立個page了,而後新建立的page 父節點指向原來的節點.最後page->add(obj).

3.3  objc_autoreleasePoolPop(atautoreleasepoolobj)方法實現:

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

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            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);
            }
        }
        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }
複製代碼

這裏面:

stop = (id *)token;
 if (*stop != POOL_BOUNDARY) {
 }複製代碼

進行判斷,pop何時結束,確定是自動釋放池頁pop到那個哨兵對象的時候纔算所有釋放完,因此*stop != POOL_BOUNDARY,除非是壞節點,要否則*stop == POOL_BOUNDARY,

緊接着popPage<false>(token, page, stop);

popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            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();
            }
        }
    }
複製代碼

將自動釋放池頁 kill,包括子頁child,父頁parent 都進行kill.使全部的autorlease對象都進行釋放.

四. 總結

  • 在APP中,整個主線程是運行在一個自動釋放池中的。

  • main函數中的自動釋放池的做用:這個池塊給出了一個pop點來顯式的告訴咱們這裏有一個釋放點,若是你的main在初始化的過程當中有別的內容能夠放在這裏。

  • 使用@autoreleasepool標記,調用push()方法。

  • 沒有hotpage,調用

    (),設置EMPTY_POOL_PLACEHOLDER

  • 由於設置了EMPTY_POOL_PLACEHOLDER,因此會設置本頁爲hotpage,添加邊界標記POOL_BOUNDARY,最後添加obj。

  • 繼續有對象調用autorelease,此時已經有了page,調用page->add(obj)

  • 若是page滿了,調用autoreleaseFullPage()建立新page,重複第6點。

  • 到達autoreleasePool邊界,調用pop方法,一般狀況下會釋放掉POOL_BOUNDARY以後的全部對象

  •  
    相關文章
    相關標籤/搜索