AutoreleasePool

原文:https://zhuanlan.zhihu.com/p/520047056
在原文基礎(chǔ)上增加了一些注釋塞耕。
說(shuō)明:源碼在runtime源碼中砾肺,不同runloop源碼中驼鞭。

使用

使用場(chǎng)景

在ARC下,AutoreleasePool主要應(yīng)用大量創(chuàng)建臨時(shí)對(duì)象的場(chǎng)景屯阀,通過(guò)AutoreleasePool控制內(nèi)存峰值救欧,是一個(gè)很好的選擇值戳。

NSAutoreleasePool

在MRC可以調(diào)用NSAutoreleasePool使對(duì)象延遲釋放西篓,在ARC下這個(gè)API已經(jīng)被禁用愈腾。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// ...
[pool release];

@autoreleasepool

除了NSAutoreleasePool還可以使用@autoreleasepool,并且蘋果推薦使用@autoreleasepool岂津,因?yàn)檫@個(gè)API性能更好虱黄,在ARC下依然可以使用@autoreleasepool。

無(wú)論是MRC還是ARC吮成,autorelease最大的作用橱乱,是在大量創(chuàng)建對(duì)象的同時(shí),通過(guò)修飾讓內(nèi)存得到提前釋放粱甫,從而降低內(nèi)存峰值泳叠。

for (size_t i = 0; i < frameCount; i++) {
    @autoreleasepool {
        [bitmapRep setProperty:NSImageCurrentFrame withValue:@(i)];
        float frameDuration = [[bitmapRep valueForProperty:NSImageCurrentFrameDuration] floatValue];
        NSImage *frameImage = [[NSImage alloc] initWithCGImage:bitmapRep.CGImage size:CGSizeZero];
        SDWebImageFrame *frame = [SDWebImageFrame frameWithImage:frameImage duration:frameDuration];
        [frames addObject:frame];
    }
}

__autoreleasing

在ARC下,需要被自動(dòng)釋放的對(duì)象茶宵,可以用__autoreleasing修飾危纫,讓對(duì)象延遲釋放。

+ (NSArray *)parseString:(NSString *)originalM3U8Str m3u8Host:(NSString *)m3u8url error:(NSError *__autoreleasing *)errorPtr;

源碼分析

__AtAutoreleasePool結(jié)構(gòu)體

struct __AtAutoreleasePool {
    __AtAutoreleasePool() {
        //在創(chuàng)建的時(shí)候會(huì)執(zhí)行objc_autoreleasePoolPush函數(shù)
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() {
        //在釋放的時(shí)候會(huì)執(zhí)行析構(gòu)函數(shù)乌庶,并執(zhí)行objc_autoreleasePoolPop函數(shù)
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
};

@autoreleasepool本質(zhì)上會(huì)被系統(tǒng)轉(zhuǎn)換成C++的__AtAutoreleasePool結(jié)構(gòu)體种蝶,@autoreleasepool的大括號(hào)開(kāi)始,對(duì)應(yīng)著objc_autoreleasePoolPush函數(shù)瞒大。大括號(hào)結(jié)束螃征,對(duì)應(yīng)著objc_autoreleasePoolPop函數(shù)。通過(guò)clang命令將OC代碼轉(zhuǎn)成C++代碼糠赦,可以看到有一個(gè)__AtAutoreleasePool結(jié)構(gòu)體会傲。

__AtAutoreleasePool結(jié)構(gòu)體在創(chuàng)建的時(shí)候會(huì)執(zhí)行objc_autoreleasePoolPush函數(shù),在釋放的時(shí)候會(huì)執(zhí)行析構(gòu)函數(shù)拙泽,并執(zhí)行objc_autoreleasePoolPop函數(shù)淌山。在這兩個(gè)函數(shù)內(nèi)部,會(huì)調(diào)用AutoreleasePoolPage的push和pop函數(shù)顾瞻。

AutoreleasePoolPage

運(yùn)行時(shí)代碼中泼疑,objc_autoreleasePoolPop和objc_autoreleasePoolPush,都調(diào)用了AutoreleasePoolPage類的實(shí)現(xiàn)荷荤。

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

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

說(shuō)明:AutoreleasePoolPage的實(shí)現(xiàn)在runtime的源碼里退渗。

在AutoreleasePoolPage的定義中,可以看到有parent和child的定義蕴纳,當(dāng)page中對(duì)象太多存儲(chǔ)不下時(shí)会油,會(huì)創(chuàng)建其他的page對(duì)象來(lái)存儲(chǔ),AutoreleasePoolPage的結(jié)構(gòu)是一個(gè)雙向鏈表古毛。在插入新的autorelease對(duì)象時(shí)翻翩,也會(huì)從鏈表頭向后查找都许,直到找到未滿的page

class AutoreleasePoolPage 
{
    magic_t const magic;                // 校驗(yàn)page的結(jié)構(gòu)是否完整
    id *next;                           // 指向下一個(gè)可以存放autorelease對(duì)象的地址
    pthread_t const thread;             // 當(dāng)前所在的線程
    AutoreleasePoolPage * const parent; // 當(dāng)前page的父節(jié)點(diǎn)
    AutoreleasePoolPage *child;         // 當(dāng)前page的子節(jié)點(diǎn)
    uint32_t const depth;               // page的深度
    uint32_t hiwat;
}

AutoreleasePoolPage是一個(gè)C++的類嫂冻,每個(gè)page占4096個(gè)字節(jié)胶征,也就是16進(jìn)制的0x1000,也就是4kb的空間桨仿。這些空間中睛低,其自身的成員變量只占56個(gè)字節(jié),也就是下面七個(gè)成員變量服傍,每個(gè)占8字節(jié)钱雷,總共56個(gè)字節(jié)其他的四千多個(gè)字節(jié)伴嗡,都用來(lái)存放被autorelease修飾的對(duì)象內(nèi)存地址急波。

POOL_BOUNDARY

image.png

POOL_BOUNDARY的作用是,區(qū)分不同的自動(dòng)釋放池瘪校,也就是不同的@autoreleasepool澄暮。調(diào)用push時(shí),會(huì)傳入POOL_BOUNDARY并返回一個(gè)地址例如0x1038阱扬,0x1038是不存儲(chǔ)@autorelease對(duì)象的地址的泣懊,起到一個(gè)標(biāo)識(shí)作用,用來(lái)分割不同的@autoreleasepool麻惶。

調(diào)用pop時(shí)馍刮,會(huì)傳入end的地址,并從后到前調(diào)用對(duì)象的release方法窃蹋,直到POOL_BOUNDARY為止卡啰。如果存在多個(gè)page,會(huì)從child的page的最末尾開(kāi)始調(diào)用警没,直到POOL_BOUNDARY匈辱。page的結(jié)構(gòu)是一個(gè)棧結(jié)構(gòu),釋放的時(shí)候也是從棧頂開(kāi)始釋放杀迹。

next指針指向棧頂亡脸,是棧里面很常見(jiàn)的一個(gè)設(shè)計(jì)。AutoreleasePoolPage和POOL_BOUNDARY的區(qū)別在于树酪,AutoreleasePoolPage負(fù)責(zé)維護(hù)存儲(chǔ)區(qū)域浅碾,而POOL_BOUNDARY則負(fù)責(zé)分割存儲(chǔ)在page中的對(duì)象地址,以@autoreleasepool為單位進(jìn)行分割续语。

多層嵌套

@autoreleasepool {
    NSObject *p1 = [[NSObject alloc] init];
    NSObject *p2 = [[NSObject alloc] init];
    @autoreleasepool {
        NSObject *p3 = [[NSObject alloc] init];
        @autoreleasepool {
            NSObject *p4 = [[NSObject alloc] init];
        }
    }
}

如果是多層@autoreleasepool的嵌套垂谢,會(huì)用同一個(gè)AutoreleasePoolPage對(duì)象。以下面的三個(gè)嵌套為例疮茄,在同一個(gè)page中的順序是下圖這樣埂陆。不同的@autoreleasepool以POOL_BOUNDARY做分割苛白。

image.png

push

創(chuàng)建一個(gè)autoreleasePool之后,就會(huì)調(diào)用push函數(shù)焚虱。在push函數(shù)中會(huì)判斷是否調(diào)試模式下,如果調(diào)試模式會(huì)每次生成一個(gè)新的page懂版。debug環(huán)境代碼可以直接忽略鹃栽,只保留autoreleaseFast函數(shù)。

static inline void *push() 
{
    id *dest;
    if (DebugPoolAllocation) {
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    return dest;
}

autoreleaseFast

在函數(shù)內(nèi)部躯畴,會(huì)通過(guò)hotPage獲取當(dāng)前的page民鼓,hotPage函數(shù)內(nèi)部本質(zhì)上是一個(gè)page和key的映射

  1. 如果page不為空并且有空間蓬抄,則調(diào)用page的add函數(shù)將對(duì)象添加到page中丰嘉,并將POOL_BOUNDARY添加在當(dāng)前的位置。
  2. 如果page已經(jīng)被創(chuàng)建沒(méi)有空間嚷缭,會(huì)調(diào)用autoreleaseFullPage函數(shù)創(chuàng)建新的page饮亏,并且將鏈表的末尾指向新創(chuàng)建的page。
  3. 如果沒(méi)有創(chuàng)建page阅爽,則調(diào)用autoreleaseNoPage函數(shù)創(chuàng)建一個(gè)新的page路幸,并且將當(dāng)前線程的hotPage設(shè)置為新創(chuàng)建的page。
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);
    }
}

autoreleaseFullPage

  1. 在autoreleaseFullPage函數(shù)中付翁,會(huì)從page的鏈表中简肴,從前往后找到末尾的節(jié)點(diǎn)
  2. 創(chuàng)建一個(gè)新的page百侧,在創(chuàng)建函數(shù)AutoreleasePoolPage中會(huì)處理parent和child指針的問(wèn)題砰识,返回的page可以直接用。
  3. 調(diào)用setHotPage將page設(shè)置到哈希表中佣渴,并且調(diào)用page的add函數(shù)將autorelease修飾的對(duì)象辫狼,添加到page中。
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
    do {
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
    } while (page->full());

    setHotPage(page);
    return page->add(obj);
}

疑惑點(diǎn):autorelease修飾的對(duì)象是指哪里
參考文章:http://www.reibang.com/p/d0558e4b0d21
說(shuō)明:當(dāng)一個(gè)對(duì)象發(fā)送了autorelease消息观话,就是將當(dāng)前這個(gè)對(duì)象加入到AutoreleasePoolPage的棧頂next指向的位置
示例代碼如下:

// MRC
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];

// ARC
@autoreleasepool {
    id obj = [NSObject alloc] init];
}

autoreleaseNoPage

static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    
    if (pushExtraBoundary) {
        page->add(POOL_BOUNDARY);
    }
    return page->add(obj);
}

autoreleaseNoPage函數(shù)的核心代碼比較簡(jiǎn)單予借,就是創(chuàng)建一個(gè)新的page,隨后設(shè)置POOL_BOUNDARY標(biāo)志频蛔,并且把對(duì)象添加進(jìn)去灵迫。在函數(shù)中需要留意POOL_BOUNDARY標(biāo)志,很多地方都用來(lái)做page是否為空的判斷晦溪。

add

id *add(id obj)
{
    ASSERT(!full());
    unprotect();
    id *ret = next;  
    *next++ = obj;
    protect();
    return ret;
}

add函數(shù)比較簡(jiǎn)單瀑粥,核心邏輯就是將obj放入next指針的位置,并且對(duì)next指針進(jìn)行++三圆,指向下一個(gè)位置狞换。*next++表示先用后加避咆,先將obj存入next的地址,隨后+1修噪。

pop

static inline void
pop(void *token)
{
    AutoreleasePoolPage *page;
    id *stop;
    // 1.
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        page = hotPage();
        if (!page) {
            return setHotPage(nil);
        }
        page = coldPage();
        token = page->begin();
    } else {
        page = pageForPointer(token);
    }
    
    // 2.
    stop = (id *)token;
    if (*stop != POOL_BOUNDARY) {
        if (stop == page->begin()  &&  !page->parent) {
        } else {
            return badPop(token);
        }
    }
    
    // 3.
    return popPage<false>(token, page, stop);
}

調(diào)用pop函數(shù)時(shí)蝇摸,有三步處理。

  1. 判斷autoreleasepool是否為空箍鼓,通過(guò)EMPTY_POOL_PLACEHOLDER占位符判斷饵蒂,為空則清空這個(gè)page。
  2. 傳入的stop是否不等于POOL_BOUNDARY標(biāo)識(shí)脏款,如果不等于則可能是一個(gè)有問(wèn)題的page围苫。
  3. 調(diào)用popPage方法,釋放對(duì)象撤师。

popPage

static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    page->releaseUntil(stop);

    if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}
  1. popPage函數(shù)核心代碼就是調(diào)用releaseUntil函數(shù)剂府,在最開(kāi)始會(huì)調(diào)用releaseUntil函數(shù)去完成釋放操作。
  2. 按照page達(dá)到一半就擴(kuò)容的原則剃盾,后面的if語(yǔ)句會(huì)判斷執(zhí)行pop后page鏈表的狀態(tài)腺占。
    2.1 如果少于半滿,就將子節(jié)點(diǎn)刪除万俗。
    2.2 如果大于半滿湾笛,則保留子節(jié)點(diǎn),并刪除后面的節(jié)點(diǎn)闰歪。

releaseUntil

void releaseUntil(id *stop) 
{
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();//獲取當(dāng)前的page
        
        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);
}

releaseUntil函數(shù)內(nèi)部嚎研,核心邏輯是從當(dāng)前page,從后到前調(diào)用objc_release库倘,釋放被autorelease修飾的對(duì)象临扮。

獲取當(dāng)前的hotPage。
判斷page是否為空教翩,如果為空則表示里面的對(duì)象被釋放完杆勇,則將page的父節(jié)點(diǎn)page設(shè)置為hotPage。
獲得上一個(gè)節(jié)點(diǎn)饱亿,->的算數(shù)優(yōu)先級(jí)比--要高蚜退,所以是先通過(guò)next獲取當(dāng)前節(jié)點(diǎn)地址,這是一個(gè)為空的待存入節(jié)點(diǎn)彪笼,隨后執(zhí)行--操作獲取上一個(gè)對(duì)象地址钻注。
通過(guò)memset將上一個(gè)節(jié)點(diǎn)釋放
判斷上一個(gè)節(jié)點(diǎn)是否占位符號(hào)POOL_BOUNDARY配猫,如果不是則調(diào)用objc_release釋放對(duì)象幅恋。
在while循環(huán)結(jié)束后,將當(dāng)前page設(shè)置為hotPage泵肄。

autorelease

static inline id autorelease(id obj)
{
    assert(!obj->isTaggedPointer());
    id *dest __unused = autoreleaseFast(obj);
    return obj;
}

對(duì)象調(diào)用autorelease方法會(huì)被編譯器轉(zhuǎn)換為objc_autoreleaseReturnValue方法捆交,并且經(jīng)過(guò)多層調(diào)用淑翼,會(huì)來(lái)到底層的autorelease函數(shù)

在這個(gè)函數(shù)中會(huì)判斷傳入的對(duì)象是否tagged pointer品追,因?yàn)閠agged pointer沒(méi)有引用計(jì)數(shù)的概念玄括。隨后會(huì)調(diào)用autoreleaseFast函數(shù),函數(shù)內(nèi)部調(diào)用add函數(shù)將obj對(duì)象加入到page中诵盼,并且會(huì)判斷是否需要?jiǎng)?chuàng)建新的page惠豺。

hotPage、coldPage

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

hotPage可以被理解為风宁,page鏈表的末尾,也就是調(diào)用push函數(shù)被插入的位置蛹疯。執(zhí)行hotPage函數(shù)獲取戒财,以及調(diào)用setHotPage設(shè)置,都是操作鏈表的末尾page捺弦。

AutoreleasePoolPage對(duì)象和線程一一對(duì)應(yīng)饮寞,并且都被存儲(chǔ)在tls的哈希表中。通過(guò)tls_get_direct函數(shù)并傳入key可以獲取到對(duì)應(yīng)的自動(dòng)釋放池幽崩。

hotPage函數(shù)中的判斷是下面的定義,這個(gè)標(biāo)示意思是當(dāng)前page為空寞钥,也就是從未存儲(chǔ)過(guò)任何對(duì)象慌申。是一個(gè)標(biāo)志位,下面是標(biāo)志位的定義理郑。

# define EMPTY_POOL_PLACEHOLDER ((id*)1)

coldPage

static inline AutoreleasePoolPage *coldPage() 
{
    AutoreleasePoolPage *result = hotPage();
    if (result) {
        while (result->parent) {
            result = result->parent;
            result->fastcheck();
        }
    }
    return result;
}

coldPage只有獲取函數(shù)蹄溉,沒(méi)有設(shè)置函數(shù)。這是因?yàn)?code>coldPage函數(shù)本質(zhì)上您炉,就是尋找page鏈表的根節(jié)點(diǎn)柒爵,從源碼中的while循環(huán)可以看到。

調(diào)試

_objc_autoreleasePoolPrint

如果想調(diào)試自動(dòng)釋放池赚爵,可以通過(guò)_objc_autoreleasePoolPrint私有API來(lái)進(jìn)行棉胀。將項(xiàng)目改為MRC,并且在命令行項(xiàng)目中增加下面這些調(diào)試代碼冀膝。

int main(int argc, const char * argv[]) {
    _objc_autoreleasePoolPrint();     // print1
    @autoreleasepool {
        _objc_autoreleasePoolPrint(); // print2
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        _objc_autoreleasePoolPrint(); // print3
    }
    _objc_autoreleasePoolPrint();     // print4
    return 0;
}

打印結(jié)果如下唁奢,可以看到POOL_BOUNDARY在page中也占了一個(gè)位置。

objc[68122]: ############## (print1)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 0 releases pending. // 當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象
objc[68122]: [0x102802000]  ................  PAGE  (hot) (cold)
objc[68122]: ##############

objc[68122]: ############## (print2)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 1 releases pending. // 當(dāng)前自動(dòng)釋放池中有1個(gè)對(duì)象畸写,這個(gè)對(duì)象為POOL_BOUNDARY
objc[68122]: [0x102802000]  ................  PAGE  (hot) (cold)
objc[68122]: [0x102802038]  ################  POOL 0x102802038  //POOL_BOUNDARY
objc[68122]: ##############

objc[68122]: ############## (print3)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 3 releases pending. // 當(dāng)前自動(dòng)釋放池中有3個(gè)對(duì)象
objc[68122]: [0x102802000]  ................  PAGE  (hot) (cold)
objc[68122]: [0x102802038]  ################  POOL 0x102802038  //POOL_BOUNDARY
objc[68122]: [0x102802040]       0x100704a10  HTPerson          //p1
objc[68122]: [0x102802048]       0x10075cc30  HTPerson          //p2
objc[68122]: ##############

objc[68156]: ############## (print4)
objc[68156]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68156]: 0 releases pending. // 當(dāng)前自動(dòng)釋放池中沒(méi)有任何對(duì)象驮瞧,因?yàn)锧autoreleasepool作用域結(jié)束,調(diào)用pop方法釋放了對(duì)象
objc[68156]: [0x100810000]  ................  PAGE  (hot) (cold)
objc[68156]: ##############

UIApplicationMain

項(xiàng)目中經(jīng)常會(huì)看到下面的代碼枯芬,很多人的解釋是“這個(gè)autoreleasepool是為了釋放主線程的autorelease對(duì)象的”论笔。但是采郎,這個(gè)說(shuō)法是錯(cuò)誤的。autoreleasepool只負(fù)責(zé)自己作用域中添加的對(duì)象狂魔,而主線程在運(yùn)行過(guò)程中蒜埋,也會(huì)隱式創(chuàng)建autoreleasepool對(duì)象,這個(gè)pool是包含在main函數(shù)的pool里面的最楷。

所以整份,主線程runloop每次執(zhí)行循環(huán)后,釋放的對(duì)象是主線程的籽孙。而main函數(shù)的autoreleasepool釋放的烈评,是main函數(shù)中直接創(chuàng)建的對(duì)象。

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

釋放時(shí)機(jī)

區(qū)分

如果是在viewDidLoad方法中創(chuàng)建一個(gè)autorelease對(duì)象犯建,并不是在這個(gè)方法結(jié)束后釋放對(duì)象讲冠,這個(gè)說(shuō)法是錯(cuò)誤的。即便執(zhí)行到viewDidAppear适瓦,依然不會(huì)釋放對(duì)象竿开。

被autorelease修飾的對(duì)象,釋放時(shí)機(jī)有兩種玻熙。

  1. 如果通過(guò)代碼添加一個(gè)autoreleasepool否彩,在作用域結(jié)束時(shí),隨著pool的釋放嗦随,就會(huì)釋放pool中的對(duì)象列荔。這種情況是及時(shí)釋放的,并不依賴于runloop称杨。
  2. 另一種就是由系統(tǒng)自動(dòng)進(jìn)行釋放肌毅,系統(tǒng)會(huì)在runloop開(kāi)始的時(shí)候創(chuàng)建一個(gè)pool,結(jié)束的時(shí)候會(huì)對(duì)pool中的對(duì)象執(zhí)行release操作姑原。

runloop

如果是系統(tǒng)創(chuàng)建的pool悬而,需要手動(dòng)開(kāi)啟runloop,主線程默認(rèn)已經(jīng)開(kāi)啟并運(yùn)行锭汛,子線程需要調(diào)用currentRunLoop方法開(kāi)啟并運(yùn)行runloop笨奠,子線程中系統(tǒng)創(chuàng)建pool的流程才會(huì)正常工作。

image.png

包括主線程在內(nèi)的每個(gè)線程唤殴,如果在線程中使用到了AutoreleasePool般婆,則會(huì)創(chuàng)建兩個(gè)Observer并添加到當(dāng)前線程的Runloop中,通過(guò)這兩個(gè)Observer進(jìn)行對(duì)象的自動(dòng)內(nèi)存管理朵逝。

// activities = 0x1蔚袍,kCFRunLoopEntry
<CFRunLoopObserver 0x60000012f000 [0x1135c2bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10eee6276)}
// activities = 0xa0,kCFRunLoopBeforeWaiting | kCFRunLoopExit
<CFRunLoopObserver 0x60000012ef60 [0x1135c2bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10eee6276)}

首先會(huì)創(chuàng)建一個(gè)Observer并監(jiān)聽(tīng)kCFRunLoopEntry消息,時(shí)機(jī)是在進(jìn)入Runloop前啤咽,此Observer的優(yōu)先級(jí)設(shè)置為-2147483647的最高優(yōu)先級(jí)晋辆,以保證回調(diào)發(fā)生在Runloop其他事件前。

然后創(chuàng)建另一個(gè)Observer宇整,并監(jiān)聽(tīng)kCFRunLoopBeforeWaiting和kCFRunLoopExit消息瓶佳,時(shí)機(jī)分別在進(jìn)入Runloop休眠和退出Runloop時(shí),將Observer的優(yōu)先級(jí)設(shè)置為2147483647鳞青,以保證回調(diào)發(fā)生在Runloop其他事件之后霸饲。

兩個(gè)Observer都有相同的回調(diào)函數(shù)_wrapRunLoopWithAutoreleasePoolHandler,在第一次回調(diào)時(shí)會(huì)在內(nèi)部調(diào)用_objc_autoreleasePoolPush函數(shù)臂拓,創(chuàng)建自動(dòng)釋放池厚脉。

kCFRunLoopBeforeWaiting將要進(jìn)入休眠前,調(diào)用_objc_autoreleasePoolPop函數(shù)釋放自動(dòng)釋放池中的對(duì)象胶惰,并調(diào)用_objc_autoreleasePoolPush函數(shù)創(chuàng)建一個(gè)新的釋放池器仗。在kCFRunLoopExit將要退出Runloop時(shí)調(diào)用_objc_autoreleasePoolPop函數(shù),釋放自動(dòng)釋放池中的對(duì)象童番。

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市威鹿,隨后出現(xiàn)的幾起案子剃斧,更是在濱河造成了極大的恐慌,老刑警劉巖忽你,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幼东,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡科雳,警方通過(guò)查閱死者的電腦和手機(jī)根蟹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)糟秘,“玉大人简逮,你說(shuō)我怎么就攤上這事∧蜃” “怎么了散庶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凌净。 經(jīng)常有香客問(wèn)我悲龟,道長(zhǎng),這世上最難降的妖魔是什么冰寻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任须教,我火速辦了婚禮,結(jié)果婚禮上斩芭,老公的妹妹穿的比我還像新娘轻腺。我一直安慰自己乐疆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布约计。 她就那樣靜靜地躺著诀拭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煤蚌。 梳的紋絲不亂的頭發(fā)上耕挨,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音尉桩,去河邊找鬼筒占。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜘犁,可吹牛的內(nèi)容都是我干的翰苫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼这橙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奏窑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起屈扎,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤埃唯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鹰晨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體墨叛,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年模蜡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漠趁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忍疾,死狀恐怖闯传,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝昆,我是刑警寧澤丸边,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站荚孵,受9級(jí)特大地震影響妹窖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜收叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一骄呼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蜓萄、人聲如沸隅茎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辟犀。三九已至,卻和暖如春绸硕,著一層夾襖步出監(jiān)牢的瞬間堂竟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工玻佩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留出嘹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓咬崔,卻偏偏與公主長(zhǎng)得像税稼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子垮斯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容