經(jīng)歷過MRC時代的開發(fā)者,肯定都用過autorelease
方法,用于把對象交給AutoreleasePool
管理胜榔,在合適的時候胳喷,自動釋放對象。其實所謂的自動釋放對象夭织,就是對所管理的對象調(diào)用release
方法吭露。要想知道autorelease
方法的原理,首先就需要弄清楚AutoreleasePool
是個什么東東尊惰。
下面來看一個段MRC環(huán)境下的代碼奴饮,為什么要在MRC下討論這個問題呢?因為ARC會為我們在合適的地方自動加上autorelease
代碼择浊,并且不允許我們手動調(diào)用該方法了戴卜,為了方便研究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
****************** 打印結(jié)果 *******************
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
概括一下看到的表面現(xiàn)象:CLPerson
實例對象p
是在@autoreleasepool {}
大括號結(jié)束的時候被釋放的投剥。
那么@autoreleasepool {}
到底做了什么呢?我們在命令行窗口里對main.m
文件執(zhí)行如下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
在生成的中間代碼main.cpp
中担孔,找到main
函數(shù)的底層實現(xiàn)如下
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;
}
其實如果你熟悉消息機制江锨,上述的代碼可以轉(zhuǎn)化成如下形式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
CLPerson *p = [[[CLPerson alloc] init] autorelease];
}
return 0;
}
我們觀察可發(fā)現(xiàn)@autoreleasepool {}
經(jīng)過編譯之后發(fā)生了如下轉(zhuǎn)變
這里多了個__AtAutoreleasePool
,它其實是個c++的結(jié)構(gòu)體糕篇,可以在main.cpp
里搜索到它的定義如下
struct __AtAutoreleasePool {
//構(gòu)造函數(shù)-->可以類比成OC的init方法啄育,在創(chuàng)建時調(diào)用
__AtAutoreleasePool()
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析構(gòu)函數(shù)-->可以類比成OC的dealloc方法,在銷毀時調(diào)用
~__AtAutoreleasePool()
{
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
如果你還不了解C++語法也無妨拌消,它跟OC的類相似挑豌,可以有函數(shù)(方法),上面的這個結(jié)構(gòu)體__AtAutoreleasePool
里面有已經(jīng)有兩個函數(shù)墩崩,
- 一個構(gòu)造函數(shù)
__AtAutoreleasePool()
-->atautoreleasepoolobj = objc_autoreleasePoolPush();
氓英,結(jié)構(gòu)體被創(chuàng)建時調(diào)用,用于結(jié)構(gòu)體的初始化 - 一個析構(gòu)函數(shù)
~__AtAutoreleasePool()
-->objc_autoreleasePoolPop(atautoreleasepoolobj);
鹦筹,結(jié)構(gòu)體被銷毀時調(diào)用
再回到我們的main
函數(shù)铝阐,其實它本質(zhì)上就是下面這個形式
@autoreleasepool {}
的情況,那么如果有多層@autoreleasepool {}
嵌套在一起铐拐,就可以按照同樣的規(guī)則來拆解objc_autoreleasePoolPush() & objc_autoreleasePoolPop()
接下來我們就來探究一下這兩個函數(shù)的實現(xiàn)邏輯徘键。在objc4源碼的NSObject.mm
文件里可以找到它們的實現(xiàn)
*************** NSObject.mm (objc4) ******************
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
可以看到,它們分別調(diào)用了C++類 AutoreleasePoolPage
的push()
和pop()
函數(shù)遍蟋。要想繼續(xù)深入后續(xù)函數(shù)的實現(xiàn)邏輯吹害,我們需要先來看一看這個AutoreleasePoolPage
的內(nèi)部結(jié)構(gòu),它的內(nèi)容不少匿值,有大量函數(shù)赠制,但是我們首先需要理清楚它的成員變量赂摆,這些是可變化的挟憔,可操控的钟些,所以去掉函數(shù)和一些靜態(tài)常量,可以將AutoreleasePoolPage
結(jié)構(gòu)簡化如下
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
根據(jù)其命名绊谭,中文釋義成自動釋放池頁政恍,有個頁的概念。我們知道自動釋放池达传,是用來存放對象的篙耗,這個“頁”就說明釋放池的結(jié)構(gòu)體應(yīng)該有頁面篇幅限制(內(nèi)存空間大小)宪赶。具體多大呢宗弯?來看一下AutoreleasePoolPage
的兩個函數(shù)
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
begin()
函數(shù)返回一個指針,指向自身最后一個成員變量之后的內(nèi)存地址(相當于越過了自身所占用的內(nèi)存空間)
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()
函數(shù)欲主,得到的是一個指針邓厕,指向AutoreleasePoolPage
對象地址之后的第4096
個字節(jié)的內(nèi)存地址。
通過以上掌握的信息扁瓢,我們先拋出結(jié)論详恼,然后再繼續(xù)通過源碼加深理解。
每個
AutoreleasePoolPage
對象占4096
個字節(jié)引几,其中成員變量共占用8字節(jié)
*7
=56個字節(jié)
昧互。剩余的4040
個字節(jié)的空間就是用來存儲自動釋放對象的。因為一個
AutoreleasePoolPage
對象的內(nèi)存是有限的伟桅,程序里面可能有很多對象會被加入自動釋放池硅堆,因此可能會出現(xiàn)多個AutoreleasePoolPage
對象來共同存放自動釋放對象。所有的AutoreleasePoolPage
對象是以雙向鏈表的形式(數(shù)據(jù)結(jié)構(gòu))連接在一起的贿讹。
AutoreleasePoolPage
對象的各成員變量含義如下
magic_t const magic;
id *next;
指向AutoreleasePoolPage
內(nèi)下一個可以用來存放自動釋放對象的內(nèi)存地址pthread_t const thread;
自動釋放池所屬的線程渐逃,說明它不能跟多個線程關(guān)聯(lián)。AutoreleasePoolPage * const parent;
指向上一頁釋放池的指針AutoreleasePoolPage *child;
指向下一頁釋放池的指針uint32_t const depth;
uint32_t hiwat;
【第一次AutoreleasePoolPage::push();】
接下來民褂,我們就正式開始研究AutoreleasePoolPage::push();
茄菊。假設(shè)我們現(xiàn)在是處在項目的main函數(shù)的第一個@autoreleasepool {}
開始的地方,也就是整個程序?qū)谝淮稳フ{(diào)用push()
函數(shù):
# define POOL_BOUNDARY nil
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {//Debug模式下,每個autorelease pool都會創(chuàng)建新頁
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {//標準情況下唤锉,調(diào)用autoreleaseFast()函數(shù)
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
其中POOL_BOUNDARY
就是nil
的宏定義嚣伐,忽略Debug模式,我們只看正常模式脊僚,那么push()
將會調(diào)用autoreleaseFast(POOL_BOUNDARY)
得到一個id *dest
并將其返回給上層函數(shù)。查看一下這個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)如果滿了增淹,則調(diào)用autoreleaseFullPage(obj, page);
return autoreleaseFullPage(obj, page);
} else {//(3)如果沒有頁面,則調(diào)用autoreleaseNoPage(obj);
return autoreleaseNoPage(obj);
}
}
因為是整個程序第一次push操作乌企,因此page對象還不存在虑润,所以會按照情況(3)走,也就是autoreleaseNoPage(obj);
加酵,實現(xiàn)如下
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
/*--"No page"
1.可以表示當前還沒有任何pool被創(chuàng)建(pushed)
2.也可以表示已經(jīng)創(chuàng)建了一個empty placeholder pool(空釋放池占位符)拳喻,只是還沒添加任何內(nèi)容
*/
assert(!hotPage());
//標簽-->是否需要增加額外的POOL_BOUNDARY
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
/*
如果存在EmptyPoolPlaceholder(空占位符pool),就修改標簽為true猪腕,
后面就需要依據(jù)此標簽增加額外的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模式陋葡,
會調(diào)用setEmptyPoolPlaceholder()設(shè)置一個EmptyPoolPlaceholder
*/
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
// 初始化第一個AutoreleasePoolPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//將其設(shè)置成當前頁(hot)
setHotPage(page);
// 根據(jù)pushExtraBoundary標簽決定是否多入棧一個POOL_BOUNDARY
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// 將傳入的obj入棧渗柿,通過 add()函數(shù)
return page->add(obj);
}
因為此時還沒有創(chuàng)建過AutoreleasePoolPage
,并且也沒有設(shè)置過EmptyPoolPlaceholder
脖岛,因此程序會命中代碼中????????標記出的代碼朵栖,調(diào)用setEmptyPoolPlaceholder();
,該函數(shù)實現(xiàn)如下
# 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
是一個靜態(tài)常量陨溅,最后將這個(id*)1
作為一個空釋放池池占位符返回,這樣整個程序的第一個push()
函數(shù)結(jié)束绍在,結(jié)果是生成了一個EMPTY_POOL_PLACEHOLDER (也就是(id*)1)
作為釋放池占位符门扇。
【第一次調(diào)用autorelease】
接著上面的過程,我們在push()
后偿渡,第一次對某個對象執(zhí)行autorelease
方法時臼寄,看一下autorelease
的內(nèi)部做了什么,先找到其源碼如下
- (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()
函數(shù)
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)如果滿了,則調(diào)用autoreleaseFullPage(obj, page);
return autoreleaseFullPage(obj, page);
} else {//(3)如果沒有頁面适揉,則調(diào)用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
綁定過嫉嘀,因此這里返回空炼邀,表明當前頁空,還未被創(chuàng)建剪侮,因此我們返回到autoreleaseFast
方法里面拭宁,將會調(diào)用autoreleaseNoPage(obj)
函數(shù),根據(jù)我們上面對這個函數(shù)步驟的注釋,這一次程序應(yīng)該會走到函數(shù)的最后一部分
- 初始化第一個
AutoreleasePoolPage
- 將其設(shè)置成當前頁(hot)
- 最初的
EMPTY_POOL_PLACEHOLDER
會使pushExtraBoundary
置為true
杰标,因此這里需要為第一個AutoreleasePoolPage
先入棧一個POOL_BOUNDARY
- 最后用
add(obj)
將傳入的自動釋放對象obj
入棧
上面add()
函數(shù)的具體功能兵怯,其實就是將obj
的值賦值給當前AutoreleasePoolPage
的next
指針指向的內(nèi)存空間,然后next
再進行++
操作在旱,移向下一段可用內(nèi)存空間,方便下一次存放自動釋放對象的時候使用推掸。如下
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)
函數(shù),實現(xiàn)如下
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
它的作用就是把當前新創(chuàng)建的AutoreleasePoolPage
與key
綁定起來谅畅,日后hotPage()
函數(shù)就可以通過key
直接拿到當前頁登渣。
【再一次調(diào)用autorelease】
如果我們繼續(xù)對新的對象執(zhí)行autorelease
操作,同樣會來到函數(shù)毡泻,但由于AutoreleasePoolPage
對象已經(jīng)存在了胜茧,如果當前page
未滿,會走如下函數(shù)
也就是直接通過
add(obj)
函數(shù)將obj
對象入棧
我們之前說過仇味,一個AutoreleasePoolPage
對象能存放的自動釋放對象數(shù)量是有限的呻顽,一個自動釋放對象就是一個指針,占8字節(jié)丹墨,而AutoreleasePoolPage
對象可用的空間是4040個字節(jié)廊遍,也就是可以存放505個對象(指針),所以一頁AutoreleasePoolPage
是有可能滿頁的贩挣,這個時候喉前,autoreleaseFast
就會調(diào)用autoreleaseFullPage(obj, page);
函數(shù),它的實現(xiàn)如下
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設(shè)置為當前頁(hot)
return page->add(obj);//通過add函數(shù)將obj存入該page
}
其實上面就是通過AutoreleasePoolPage
對象的child
指針去尋找下一個未滿的page
。AutoreleasePoolPage
對象之間是通過child
和parent
指針形成的雙向鏈表結(jié)構(gòu)王财,就是為了在這個時候使用的卵迂。同樣,在清空釋放池對象的時候绒净,如果當前釋放池完全空了见咒,則會通過parent
指針去尋找上層的釋放池。
【再一次AutoreleasePoolPage::push();】
除了系統(tǒng)在main
函數(shù)里加上的最初的一層@autoreleasepool {}
之外挂疆,有時候我們自己的代碼里面可能會也會使用@autoreleasepool {}
论颅,方便對一些對象進行更為靈活的內(nèi)存管理。那么我們手動加的@autoreleasepool {}
肯定是嵌套在main函數(shù)@autoreleasepool {}
內(nèi)部的囱嫩,相當于
int main(int argc, const char * argv[]) {
@autoreleasepool {//這是系統(tǒng)加的第一層
@autoreleasepool {}//這是我們可能會添加的內(nèi)層嵌套
}
}
現(xiàn)在我們再次來看一下這一次AutoreleasePoolPage::push();
會如何執(zhí)行恃疯。同樣程序會執(zhí)行到autoreleaseFast(POOL_BOUNDARY);
POOL_BOUNDARY
會被傳入autoreleaseFast
函數(shù),并且也會通過add()
或者autoreleaseFullPage()
被添加到AutoreleasePoolPage
對象的頁空間上墨闲。其實就是和普通的[obj autorelease]
的流程一樣今妄,只不過這次是obj
= POOL_BOUNDARY
,顯然這是為了一個新的@autoreleasepool{}
做準備。
POOL_BOUNDARY
到底是拿來干嘛的呢盾鳞?一會你就知道了犬性。
分析完了源碼,現(xiàn)在通過圖例來展示一下
@autoreleasepool
的實現(xiàn)原理腾仅。
【假設(shè)】為方便展示每頁AutoreleasePoolPage
只能存放3個釋放對象乒裆,如下
autorelease對象什么時候回調(diào)用release方法呢?
這個問題就要搞清楚@autoreleasepool{}
的另一半AutoreleasePoolPage::pop(atautoreleasepoolobj);
做了什么推励。一起來看一看
releaseUntile(stop)
鹤耍,這里的stop
實際上傳入的就是POOL_BOUNDARY
,進入該函數(shù)
void releaseUntil(id *stop)
{
while (this->next != stop) {//??如果next指向POOL_BOUNDARY验辞,跳出循環(huán)??
//??拿到當前頁
AutoreleasePoolPage *page = hotPage();
//????當前頁如果為空稿黄,通過parent拿到上一個AutoreleasePoolPage對象作為當前頁
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
//??????通過 --next 拿到當前頁棧頂?shù)膶ο? 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()
核心步驟已經(jīng)在上面函數(shù)里的注釋體現(xiàn)出來跌造。也就是說杆怕,當最內(nèi)層的@autoreleasepool{}
作用域結(jié)束調(diào)用其對應(yīng)的pop()
函數(shù)時,會從AutoreleasePoolPage
鏈表的當前頁里面找到棧頂?shù)膶ο罂翘埃饌€開始釋放陵珍,直到遇到POOL_BOUNDARY
就停下來,這樣违施,就代表這一層的@autorelease{}
內(nèi)所包含的所有對象都完成了release
方法調(diào)用撑教。
當程序走到上一層的@autoreleasepool{}
作用域結(jié)束的地方,又回執(zhí)行上面的流程醉拓,對其包含的對象一次調(diào)用release
方法伟姐。可以通過下圖的示例來體會一下亿卤。
AutoreleasePool與RunLoop
通過上面的研究愤兵,我們知道@autoreleasepool{}
的作用,實際上就是在作用域的頭和尾分別調(diào)用了objc_autoreleasePoolPush();
和objc_autoreleasePoolPop()
函數(shù)排吴,但是在iOS項目當中秆乳,@autoreleasepool{}
的作用域是什么時候開始,什么時候結(jié)束呢钻哩?這就需要了解我們之前研究過的另一個知識點RunLoop屹堰。我們知道,除非我們手動啟動子線程的RunLoop街氢,否則程序里面只有主線程有RunLoop扯键,這是系統(tǒng)默認開啟的。下面我們來看一下主線程的RunLoop肚子里都有什么寶貝珊肃。
我們可以隨便新建一個iOS項目荣刑,在ViewController
的viewDidLoad
方法里可以直接打印當前RunLoop對象(即主線程的RunLoop對象)
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
@end
打印結(jié)果是洋洋灑灑的一大堆馅笙,如果你還不熟悉RunLoop的結(jié)構(gòu),可以參考我的Runloop的內(nèi)部結(jié)構(gòu)與運行原理厉亏,里面應(yīng)該說的比較清楚了董习。我們可以在打印結(jié)果的common mode items
部分,找到兩個跟autorelease
相關(guān)的observer
爱只,如下圖所示
具體如下
<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> )
}
}
我們可以看到皿淋,這兩個監(jiān)聽器分監(jiān)聽的狀態(tài)分別是
-
activities = 0xa0
(對應(yīng)十進制的160
) -
activities = 0x1
(對應(yīng)十進制的1
)
這兩個狀態(tài)怎么解讀呢?我們可以在CF框架的RunLoop源碼里面找到對應(yīng)的定義
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
};
根據(jù)RunLoop狀態(tài)的枚舉值可以看出恬试,160 = 128 + 32
窝趣,也就是說
-
activities = 0xa0
=(kCFRunLoopExit | kCFRunLoopBeforeWaiting) -
activities = 0x1
=(kCFRunLoopEntry)
因此這三個狀態(tài)被監(jiān)聽到的時候,就會調(diào)用_wrapRunLoopWithAutoreleasePoolHandler
函數(shù)忘渔。這個函數(shù)實際上是按照下圖的示意運作 - 監(jiān)聽到kCFRunLoopEntry事件高帖,調(diào)用
objc_autoreleasePoolPush();
- 監(jiān)聽到kCFRunLoopBeforeWaiting事件缰儿,調(diào)用
objc_autoreleasePoolPop()
畦粮,然后調(diào)用objc_autoreleasePoolPush();
- 監(jiān)聽到kCFRunLoopExit事件,調(diào)用
objc_autoreleasePoolPop()
根據(jù)上面的分析乖阵,我們可以總結(jié)宣赔,除了程序啟動(對應(yīng)kCFRunLoopEntry)和程序退出(對應(yīng)kCFRunLoopExit)會調(diào)用一次objc_autoreleasePoolPush();
和objc_autoreleasePoolPop()
外,程序的運行過程中瞪浸,每當RunLoop即將休眠儒将,被observer
監(jiān)聽到kCFRunLoopBeforeWaiting狀態(tài)時,會先調(diào)用一次objc_autoreleasePoolPop()
对蒲,這樣就將當前的autoreleasepool
里面的對象逐個調(diào)用release
方法钩蚊,相當于清空釋放池子;緊接著再調(diào)用一次objc_autoreleasePoolPush();
蹈矮,相當于開啟一個新的釋放池砰逻,等待RunLoop醒來后的下一次循環(huán)使用。
自動釋放池的對象什么時候會被調(diào)用release方法呢泛鸟?
RunLoop的每一圈循環(huán)過程中蝠咆,調(diào)用過autorelease
方法的對象(也就是被加入AutoreleasePoolPage
的對象),會在當次循環(huán)即將進入休眠狀態(tài)的時候北滥,被調(diào)用release
方法,也可以說是被釋放了刚操。
好了,AutoreleasePool的原理以及它和RunLoop的關(guān)系就分析到這里再芋。