內存管理剖析(四)——autorelease原理分析

內存管理傳送門🦋🦋🦋

內存管理剖析(一)—MRC時代的手動內存管理c++

內存管理剖析(二)——定時器問題markdown

內存管理剖析(三)——iOS程序的內存佈局數據結構

內存管理剖析(五)—— weak指針實現原理框架

經歷過MRC時代的開發者,確定都用過autorelease方法,用於把對象交給AutoreleasePool管理,在合適的時候,自動釋放對象。其實所謂的自動釋放對象,就是對所管理的對象調用release方法。要想知道autorelease方法的原理,首先就須要弄清楚AutoreleasePool是個什麼東東。iphone

下面來看一個段MRC環境下的代碼,爲何要在MRC下討論這個問題呢?由於ARC會爲咱們在合適的地方自動加上autorelease代碼,而且不容許咱們手動調用該方法了,爲了方便研究autorelease原理,咱們仍是得回到MRC。函數

****************** main.m *****************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

int main(int argc, const char * argv[]) {

    NSLog(@"pool--start");
    @autoreleasepool { 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    } 
    NSLog(@"pool--end");

    return 0;
}

************** CLPerson.m **************
#import "CLPerson.h"

@implementation CLPerson

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [super dealloc];
}
@end

****************** 打印結果 *******************
2019-08-27 16:37:15.141523+0800 Interview16-autorelease[11602:772121] pool--start
2019-08-27 16:37:15.141763+0800 Interview16-autorelease[11602:772121] -[CLPerson dealloc]
2019-08-27 16:37:15.141775+0800 Interview16-autorelease[11602:772121] pool--end
複製代碼

歸納一下看到的表面現象:CLPerson實例對象p是在@autoreleasepool {}大括號結束的時候被釋放的。 那麼@autoreleasepool {}到底作了什麼呢?咱們在命令行窗口裏對main.m文件執行以下命令oop

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp佈局

在生成的中間代碼main.cpp中,找到main函數的底層實現以下post

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}
複製代碼

其實若是你熟悉消息機制,上述的代碼能夠轉化成以下形式ui

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { 
        __AtAutoreleasePool __autoreleasepool; 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    }
    return 0;
}
複製代碼

咱們觀察可發現@autoreleasepool {}通過編譯以後發生了以下轉變

這裏多了個__AtAutoreleasePool,它實際上是個c++的結構體,能夠在main.cpp裏搜索到它的定義以下

struct __AtAutoreleasePool {
    //構造函數-->能夠類比成OC的init方法,在建立時調用
  __AtAutoreleasePool()
    {
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    
    //析構函數-->能夠類比成OC的dealloc方法,在銷燬時調用
  ~__AtAutoreleasePool()
    {
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    
  void * atautoreleasepoolobj;
};
複製代碼

若是你還不瞭解C++語法也無妨,它跟OC的類類似,能夠有函數(方法),上面的這個結構體__AtAutoreleasePool裏面有已經有兩個函數,

  • 一個構造函數__AtAutoreleasePool() --> atautoreleasepoolobj = objc_autoreleasePoolPush();,結構體被建立時調用,用於結構體的初始化
  • 一個析構函數~__AtAutoreleasePool() --> objc_autoreleasePoolPop(atautoreleasepoolobj);,結構體被銷燬時調用

再回到咱們的main函數,其實它本質上就是下面這個形式上面是單層@autoreleasepool {}的狀況,那麼若是有多層@autoreleasepool {}嵌套在一塊兒,就能夠按照一樣的規則來拆解

objc_autoreleasePoolPush() & objc_autoreleasePoolPop()

接下來咱們就來探究一下這兩個函數的實現邏輯。在objc4源碼的NSObject.mm文件裏能夠找到它們的實現

*************** NSObject.mm (objc4) ******************
void * objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

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

能夠看到,它們分別調用了C++類 AutoreleasePoolPagepush()pop()函數。要想繼續深刻後續函數的實現邏輯,咱們須要先來看一看這個AutoreleasePoolPage的內部結構,它的內容很多,有大量函數,可是咱們首先須要理清楚它的成員變量,這些是可變化的,可操控的,因此去掉函數和一些靜態常量,能夠將AutoreleasePoolPage結構簡化以下

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
複製代碼

根據其命名,中文釋義成自動釋放池頁,有個頁的概念。咱們知道自動釋放池,是用來存放對象的,這個「頁」就說明釋放池的結構體應該有頁面篇幅限制(內存空間大小)。具體多大呢?來看一下AutoreleasePoolPage的兩個函數

id * begin() {

        return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
        return (id *) ((uint8_t *)this+SIZE);
}
複製代碼

begin()函數返回一個指針,指向自身最後一個成員變量以後的內存地址(至關於越過了自身所佔用的內存空間) end()裏面有一個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

********************************************
#define PAGE_MAX_SIZE PAGE_SIZE
********************************************
#define PAGE_SIZE I386_PGBYTES
********************************************
#define I386_PGBYTES 4096 /* bytes per 80386 page */

複製代碼

能夠看到,SIZE其實是4096。這就是說end()函數,獲得的是一個指針,指向AutoreleasePoolPage對象地址以後的第4096個字節的內存地址。AutoreleasePoolPage的begin()和end()

經過以上掌握的信息,咱們先拋出結論,而後再繼續經過源碼加深理解。

每一個AutoreleasePoolPage對象佔4096個字節,其中成員變量共佔用 8字節 * 7 = 56個字節。剩餘的4040個字節的空間就是用來存儲自動釋放對象的。

由於一個AutoreleasePoolPage對象的內存是有限的,程序裏面可能有不少對象會被加入自動釋放池,所以可能會出現多個AutoreleasePoolPage對象來共同存放自動釋放對象。全部的AutoreleasePoolPage對象是以雙向鏈表的形式(數據結構)鏈接在一塊兒的。

AutoreleasePoolPage對象的各成員變量含義以下

  • magic_t const magic;
  • id *next;指向AutoreleasePoolPage內下一個能夠用來存放自動釋放對象的內存地址
  • pthread_t const thread; 自動釋放池所屬的線程,說明它不能跟多個線程關聯。
  • AutoreleasePoolPage * const parent;指向上一頁釋放池的指針
  • AutoreleasePoolPage *child;指向下一頁釋放池的指針
  • uint32_t const depth;
  • uint32_t hiwat;

AutoreleasePoolPage結構示意圖

【第一次AutoreleasePoolPage::push();】

接下來,咱們就正式開始研究AutoreleasePoolPage::push();。假設咱們如今是處在項目的main函數的第一個@autoreleasepool {}開始的地方,也就是整個程序將會第一次去調用push()函數:

# define POOL_BOUNDARY nil

static inline void *push() {
        id *dest;
        if (DebugPoolAllocation) {//Debug模式下,每一個autorelease pool都會建立新頁
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {//標準狀況下,調用autoreleaseFast()函數
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
複製代碼

其中POOL_BOUNDARY就是nil的宏定義,忽略Debug模式,咱們只看正常模式,那麼push()將會調用autoreleaseFast(POOL_BOUNDARY)獲得一個id *dest並將其返回給上層函數。查看一下這個autoreleaseFast() ,看看它到底能給咱們返回什麼

static inline id *autoreleaseFast(id obj) {
        //拿到當前可用的AutoreleasePoolPage對象page
        AutoreleasePoolPage *page = hotPage();
        //(1)若是page存在&&page未滿,則直接增長obj
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {//(2)若是滿了,則調用autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {//(3)若是沒有頁面,則調用autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj);
        }
    }
複製代碼

由於是整個程序第一次push操做,所以page對象還不存在,因此會按照狀況(3)走,也就是autoreleaseNoPage(obj);,實現以下

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj) {
        
        /*--"No page" 1.能夠表示當前尚未任何pool被建立(pushed) 2.也能夠表示已經建立了一個empty placeholder pool(空釋放池佔位符),只是還沒添加任何內容 */
        assert(!hotPage());
        
        
        
        
        
        
        //標籤-->是否須要增長額外的POOL_BOUNDARY
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            /* 若是存在EmptyPoolPlaceholder(空佔位符pool),就修改標籤爲true, 後面就須要依據此標籤增長額外的POOL_BOUNDARY */
            pushExtraBoundary = true;
        }
        
        /* 若是傳入的obj不等於POOL_BOUNDARY(nil)而且找不到當前pool(丟失了),返回nil */
        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;
        }
        
        /* ♥️♥️♥️♥️若是傳入的是POOL_BOUNDARY,而且不在Debug模式, 會調用setEmptyPoolPlaceholder()設置一個EmptyPoolPlaceholder */
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            return setEmptyPoolPlaceholder();
        }
        
        
        

        // 初始化第一個AutoreleasePoolPage
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        //將其設置成當前頁(hot)
        setHotPage(page);
        
        // 根據pushExtraBoundary標籤決定是否多入棧一個POOL_BOUNDARY
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // 將傳入的obj入棧,經過 add()函數
        return page->add(obj);
    }
複製代碼

由於此時尚未建立過AutoreleasePoolPage,而且也沒有設置過EmptyPoolPlaceholder,所以程序會命中代碼中♥️♥️♥️♥️標記出的代碼,調用setEmptyPoolPlaceholder();,該函數實現以下

# define EMPTY_POOL_PLACEHOLDER ((id*)1)
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
********************************************

static inline id* setEmptyPoolPlaceholder() {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
複製代碼

能夠看到實際上就是將key(id*)1綁定起來,這個key是一個靜態常量,最後將這個(id*)1做爲一個空釋放池池佔位符返回,這樣整個程序的第一個push()函數結束,結果是生成了一個EMPTY_POOL_PLACEHOLDER (也就是(id*)1)做爲釋放池佔位符。

【第一次調用autorelease】

接着上面的過程,咱們在push()後,第一次對某個對象執行autorelease方法時,看一下autorelease的內部作了什麼,先找到其源碼以下

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


複製代碼

經過逐層遞進,咱們看到autorelease方法最終又來到了autoreleaseFast()函數

static inline id *autoreleaseFast(id obj) {
        //拿到當前可用的AutoreleasePoolPage對象page
        AutoreleasePoolPage *page = hotPage();
        //(1)若是page存在&&page未滿,則直接增長obj
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {//(2)若是滿了,則調用autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {//(3)若是沒有頁面,則調用autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj);
        }
    }
複製代碼

那麼這一次,咱們看看第一句代碼裏面hotPage();獲得的是什麼

static inline AutoreleasePoolPage *hotPage() {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        //若是檢查到key有綁定EMPTY_POOL_PLACEHOLDER,返回nil
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        
        if (result) result->fastcheck();
        return result;//將當前頁對象返回
    }
複製代碼

由於咱們一開始將keyEMPTY_POOL_PLACEHOLDER 綁定過,所以這裏返回空,代表當前頁空,還未被建立,所以咱們返回到autoreleaseFast方法裏面,將會調用autoreleaseNoPage(obj)函數,根據咱們上面對這個函數步驟的註釋,這一次程序應該會走到函數的最後一部分 主要作了下面幾件事:

  • 初始化第一個AutoreleasePoolPage
  • 將其設置成當前頁(hot)
  • 最初的EMPTY_POOL_PLACEHOLDER會使pushExtraBoundary置爲true,所以這裏須要爲第一個AutoreleasePoolPage先入棧一個POOL_BOUNDARY
  • 最後用add(obj)將傳入的自動釋放對象obj入棧

上面add()函數的具體功能,其實就是將obj的值賦值給當前AutoreleasePoolPagenext指針指向的內存空間,而後next再進行++操做,移向下一段可用內存空間,方便下一次存放自動釋放對象的時候使用。以下

id *add(id obj) {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//先賦值,再++
        protect();
        return ret;
    }
複製代碼

另外須要注意一下這裏的setHotPage(page)函數,實現以下

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

它的做用就是把當前新建立的AutoreleasePoolPagekey綁定起來,往後hotPage()函數就能夠經過key直接拿到當前頁。

【再一次調用autorelease】

若是咱們繼續對新的對象執行autorelease操做,一樣會來到函數,但因爲AutoreleasePoolPage對象已經存在了,若是當前page未滿,會走以下函數image.png 也就是直接經過add(obj)函數將obj對象入棧

咱們以前說過,一個AutoreleasePoolPage對象能存放的自動釋放對象數量是有限的,一個自動釋放對象就是一個指針,佔8字節,而AutoreleasePoolPage對象可用的空間是4040個字節,也就是能夠存放505個對象(指針),因此一頁AutoreleasePoolPage是有可能滿頁的,這個時候,autoreleaseFast 就會調用autoreleaseFullPage(obj, page);函數,它的實現以下

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 {//經過child指針拿到下一個沒有滿的page對象
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);//先將上面獲取的page設置爲當前頁(hot)
        return page->add(obj);//經過add函數將obj存入該page
    }
複製代碼

其實上面就是經過AutoreleasePoolPage對象的child指針去尋找下一個未滿的pageAutoreleasePoolPage對象之間是經過childparent指針造成的雙向鏈表結構,就是爲了在這個時候使用的。一樣,在清空釋放池對象的時候,若是當前釋放池徹底空了,則會經過parent指針去尋找上層的釋放池。

【再一次AutoreleasePoolPage::push();】

除了系統在main函數里加上的最初的一層@autoreleasepool {}以外,有時候咱們本身的代碼裏面可能會也會使用@autoreleasepool {},方便對一些對象進行更爲靈活的內存管理。那麼咱們手動加的@autoreleasepool {}確定是嵌套在main函數@autoreleasepool {}內部的,至關於

int main(int argc, const char * argv[]) {
        @autoreleasepool {//這是系統加的第一層
                @autoreleasepool {}//這是咱們可能會添加的內層嵌套
        }

}
複製代碼

如今咱們再次來看一下這一次AutoreleasePoolPage::push();會如何執行。一樣程序會執行到autoreleaseFast(POOL_BOUNDARY);POOL_BOUNDARY會被傳入autoreleaseFast函數,而且也會經過add()或者autoreleaseFullPage()被添加到AutoreleasePoolPage對象的頁空間上。其實就是和普通的[obj autorelease]的流程同樣,只不過此次是obj = POOL_BOUNDARY,顯然這是爲了一個新的@autoreleasepool{}作準備。

POOL_BOUNDARY究竟是拿來幹嗎的呢?一會你就知道了。

分析完了源碼,如今經過圖例來展現一下@autoreleasepool的實現原理。 【假設】爲方便展現每頁AutoreleasePoolPage只能存放3個釋放對象,以下

autorelease對象何時回調用release方法呢?

這個問題就要搞清楚@autoreleasepool{}的另外一半AutoreleasePoolPage::pop(atautoreleasepoolobj);作了什麼。一塊兒來看一看其中的核心函數即是releaseUntile(stop),這裏的stop實際上傳入的就是POOL_BOUNDARY,進入該函數

void releaseUntil(id *stop) {
        
        
        while (this->next != stop) {//🥝若是next指向POOL_BOUNDARY,跳出循環🥝
            
            //🥝拿到當前頁
            AutoreleasePoolPage *page = hotPage();

            //🥝🥝當前頁若是爲空,經過parent拿到上一個AutoreleasePoolPage對象做爲當前頁
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            
            //🥝🥝🥝經過 --next 拿到當前頁棧頂的對象
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
                //🥝🥝🥝🥝若是obj不是POOL_BOUNDARY,就進行[obj release]
                objc_release(obj);
            }
        }

        setHotPage(this);
    }
複製代碼

pop()核心步驟已經在上面函數裏的註釋體現出來。也就是說,當最內層的@autoreleasepool{}做用域結束調用其對應的pop()函數時,會從AutoreleasePoolPage鏈表的當前頁裏面找到棧頂的對象,逐個開始釋放,直到遇到POOL_BOUNDARY就停下來,這樣,就表明這一層的@autorelease{}內所包含的全部對象都完成了release方法調用。

當程序走到上一層的@autoreleasepool{}做用域結束的地方,又回執行上面的流程,對其包含的對象一次調用release方法。能夠經過下圖的示例來體會一下。

AutoreleasePoolPage::pop()的核心步驟


AutoreleasePool與RunLoop

經過上面的研究,咱們知道@autoreleasepool{}的做用,實際上就是在做用域的頭和尾分別調用了objc_autoreleasePoolPush();objc_autoreleasePoolPop()函數,可是在iOS項目當中,@autoreleasepool{}的做用域是何時開始,何時結束呢?這就須要瞭解咱們以前研究過的另外一個知識點RunLoop。咱們知道,除非咱們手動啓動子線程的RunLoop,不然程序裏面只有主線程有RunLoop,這是系統默認開啓的。下面咱們來看一下主線程的RunLoop肚子裏都有什麼寶貝。

咱們能夠隨便新建一個iOS項目,在ViewControllerviewDidLoad方法裏能夠直接打印當前RunLoop對象(即主線程的RunLoop對象)

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}

@end
複製代碼

打印結果是洋洋灑灑的一大堆,若是你還不熟悉RunLoop的結構,能夠參考個人Runloop的內部結構與運行原理,裏面應該說的比較清楚了。咱們能夠在打印結果的common mode items 部分,找到兩個跟autorelease相關的observer,以下圖所示 runloop中的autorelease 具體以下

<CFRunLoopObserver 0x600003f3c640 [0x10a2fdae8]>
{
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058>)
    }
}


<CFRunLoopObserver 0x600003f3c500 [0x10a2fdae8]>
{
valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058> )
    }
}
複製代碼

咱們能夠看到,這兩個監聽器分監聽的狀態分別是

  • activities = 0xa0(對應十進制的160
  • activities = 0x1(對應十進制的1

這兩個狀態怎麼解讀呢?咱們能夠在CF框架的RunLoop源碼裏面找到對應的定義

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),************十進制1---(進入loop)
    kCFRunLoopBeforeTimers = (1UL << 1),****十進制2
    kCFRunLoopBeforeSources = (1UL << 2),**十進制4
    kCFRunLoopBeforeWaiting = (1UL << 5),***十進制32----(loop即將休眠)
    kCFRunLoopAfterWaiting = (1UL << 6),*****十進制64
    kCFRunLoopExit = (1UL << 7),**************十進制128----(退出loop)
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
複製代碼

根據RunLoop狀態的枚舉值能夠看出,160 = 128 + 32,也就是說

  • activities = 0xa0 =(kCFRunLoopExitkCFRunLoopBeforeWaiting
  • activities = 0x1 =(kCFRunLoopEntry

所以這三個狀態被監聽到的時候,就會調用_wrapRunLoopWithAutoreleasePoolHandler函數。這個函數其實是按照下圖的示意運做

  • 監聽到kCFRunLoopEntry事件,調用objc_autoreleasePoolPush();
  • 監聽到kCFRunLoopBeforeWaiting事件,調用objc_autoreleasePoolPop(),而後調用objc_autoreleasePoolPush();
  • 監聽到kCFRunLoopExit事件,調用objc_autoreleasePoolPop()

根據上面的分析,咱們能夠總結,除了程序啓動(對應kCFRunLoopEntry)和程序退出(對應kCFRunLoopExit)會調用一次objc_autoreleasePoolPush();objc_autoreleasePoolPop()外,程序的運行過程當中,每當RunLoop即將休眠,被observer監聽到kCFRunLoopBeforeWaiting狀態時,會先調用一次objc_autoreleasePoolPop(),這樣就將當前的autoreleasepool裏面的對象逐個調用release方法,至關於清空釋放池子;緊接着再調用一次objc_autoreleasePoolPush();,至關於開啓一個新的釋放池,等待RunLoop醒來後的下一次循環使用。

自動釋放池的對象何時會被調用release方法呢?

RunLoop的每一圈循環過程當中,調用過autorelease方法的對象(也就是被加入AutoreleasePoolPage的對象),會在當次循環即將進入休眠狀態的時候,被調用release方法,也能夠說是被釋放了。

好了,AutoreleasePool的原理以及它和RunLoop的關係就分析到這裏。

內存管理傳送門🦋🦋🦋

內存管理剖析(一)—MRC時代的手動內存管理

內存管理剖析(二)——定時器問題

內存管理剖析(三)——iOS程序的內存佈局

內存管理剖析(五)—— weak指針實現原理

相關文章
相關標籤/搜索