天天看點

記憶體管理——autorelease原理分析

記憶體管理系列文章

記憶體管理—MRC時代的手動記憶體管理

記憶體管理—weak的實作原理

記憶體管理——autorelease原理分析

記憶體管理——定時器問題

iOS程式的記憶體布局

經曆過MRC時代的開發者,肯定都用過

autorelease

方法,用于把對象交給

AutoreleasePool

管理,在合适的時候,自動釋放對象。其實所謂的自動釋放對象,就是對所管理的對象調用

release

方法。要想知道

autorelease

方法的原理,首先就需要弄清楚

AutoreleasePool

是個什麼東東。

下面來看一個段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

檔案執行如下指令

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

在生成的中間代碼

main.cpp

中,找到

main

函數的底層實作如下

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

其實如果你熟悉消息機制,上述的代碼可以轉化成如下形式

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

我們觀察可發現

@autoreleasepool {}

經過編譯之後發生了如下轉變

記憶體管理——autorelease原理分析

這裡多了個

__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

函數,其實它本質上就是下面這個形式

記憶體管理——autorelease原理分析

上面是單層

@autoreleasepool {}

的情況,那麼如果有多層

@autoreleasepool {}

嵌套在一起,就可以按照同樣的規則來拆解

記憶體管理——autorelease原理分析
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++類

AutoreleasePoolPage

push()

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

個位元組的記憶體位址。

記憶體管理——autorelease原理分析

通過以上掌握的資訊,我們先抛出結論,然後再繼續通過源碼加深了解。

每個

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;

    記憶體管理——autorelease原理分析

【第一次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;//将目前頁對象傳回
    }
           

因為我們一開始将

key

EMPTY_POOL_PLACEHOLDER

綁定過,是以這裡傳回空,表明目前頁空,還未被建立,是以我們傳回到

autoreleaseFast

方法裡面,将會調用

autoreleaseNoPage(obj)

函數,根據我們上面對這個函數步驟的注釋,這一次程式應該會走到函數的最後一部分

記憶體管理——autorelease原理分析

主要做了下面幾件事:

  • 初始化第一個

    AutoreleasePoolPage

  • 将其設定成目前頁(hot)
  • 最初的

    EMPTY_POOL_PLACEHOLDER

    會使

    pushExtraBoundary

    置為

    true

    ,是以這裡需要為第一個

    AutoreleasePoolPage

    先入棧一個

    POOL_BOUNDARY

  • 最後用

    add(obj)

    将傳入的自動釋放對象

    obj

    入棧

上面

add()

函數的具體功能,其實就是将

obj

的值指派給目前

AutoreleasePoolPage

next

指針指向的記憶體空間,然後

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

它的作用就是把目前新建立的

AutoreleasePoolPage

key

綁定起來,日後

hotPage()

函數就可以通過

key

直接拿到目前頁。

####【再一次調用autorelease】

如果我們繼續對新的對象執行

autorelease

操作,同樣會來到函數,但由于

AutoreleasePoolPage

對象已經存在了,如果目前

page

未滿,會走如下函數

記憶體管理——autorelease原理分析

也就是直接通過

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

指針去尋找下一個未滿的

page

AutoreleasePoolPage

對象之間是通過

child

parent

指針形成的雙向連結清單結構,就是為了在這個時候使用的。同樣,在清空釋放池對象的時候,如果目前釋放池完全空了,則會通過

parent

指針去尋找上層的釋放池。

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

除了系統在

main

函數裡加上的最初的一層

@autoreleasepool {}

之外,有時候我們自己的代碼裡面可能會也會使用

@autoreleasepool {}

,友善對一些對象進行更為靈活的記憶體管理。那麼我們手動加的

@autoreleasepool {}

肯定是嵌套在main函數

@autoreleasepool {}

内部的,相當于

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

}
           

現在我們再次來看一下這一次

AutoreleasePoolPage::push();

會如何執行。同樣程式會執行到

autoreleaseFast(POOL_BOUNDARY);

記憶體管理——autorelease原理分析
記憶體管理——autorelease原理分析

POOL_BOUNDARY

會被傳入

autoreleaseFast

函數,并且也會通過

add()

或者

autoreleaseFullPage()

被添加到

AutoreleasePoolPage

對象的頁空間上。其實就是和普通的

[obj autorelease]

的流程一樣,隻不過這次是

obj

=

POOL_BOUNDARY

,顯然這是為了一個新的

@autoreleasepool{}

做準備。

POOL_BOUNDARY

到底是拿來幹嘛的呢?一會你就知道了。

分析完了源碼,現在通過圖例來展示一下

@autoreleasepool

的實作原理。

【假設】為友善展示每頁

AutoreleasePoolPage

隻能存放3個釋放對象,如下
記憶體管理——autorelease原理分析

autorelease對象什麼時候回調用release方法呢?

這個問題就要搞清楚

@autoreleasepool{}

的另一半

AutoreleasePoolPage::pop(atautoreleasepoolobj);

做了什麼。一起來看一看

記憶體管理——autorelease原理分析

其中的核心函數便是

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

方法。可以通過下圖的示例來體會一下。

記憶體管理——autorelease原理分析

AutoreleasePool與RunLoop

通過上面的研究,我們知道

@autoreleasepool{}

的作用,實際上就是在作用域的頭和尾分别調用了

objc_autoreleasePoolPush();

objc_autoreleasePoolPop()

函數,但是在iOS項目當中,

@autoreleasepool{}

的作用域是什麼時候開始,什麼時候結束呢?這就需要了解我們之前研究過的另一個知識點RunLoop。我們知道,除非我們手動啟動子線程的RunLoop,否則程式裡面隻有主線程有RunLoop,這是系統預設開啟的。下面我們來看一下主線程的RunLoop肚子裡都有什麼寶貝。

我們可以随便建立一個iOS項目,在

ViewController

viewDidLoad

方法裡可以直接列印目前RunLoop對象(即主線程的RunLoop對象)

@implementation ViewController

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

@end
           

列印結果是洋洋灑灑的一大堆,如果你還不熟悉RunLoop的結構,可以參考我的Runloop的内部結構與運作原理,裡面應該說的比較清楚了。我們可以在列印結果的

common mode items

部分,找到兩個跟

autorelease

相關的

observer

,如下圖所示

記憶體管理——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

    =(kCFRunLoopExit | kCFRunLoopBeforeWaiting)
  • activities = 0x1

    =(kCFRunLoopEntry)

    是以這三個狀态被監聽到的時候,就會調用

    _wrapRunLoopWithAutoreleasePoolHandler

    函數。這個函數實際上是按照下圖的示意運作
    記憶體管理——autorelease原理分析
  • 監聽到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的關系就分析到這裡