目錄
1. autorelease的本質(zhì)
2. autoreleasepool的源碼解析
3. autoreleasePoolPage的結(jié)構(gòu)
4. autoreleasePool的結(jié)構(gòu)和工作原理
5.autoreleasepool的嵌套
6. autorelaeasepool、NSRunLoop 、子線程三者的關(guān)系
1.autorelease的本質(zhì)
- autorelease本質(zhì)就是延遲調(diào)用release方法
MRC環(huán)境下,通過(guò)[obj autorelease]來(lái)延遲內(nèi)存的釋放
ARC環(huán)境下,是不能手動(dòng)調(diào)用,系統(tǒng)會(huì)自動(dòng)給對(duì)象添加autorelease
2.autoreleasepool的源碼解析
進(jìn)入工程main.m文件中
int main(int argc, char * argv[]) {
@autoreleasepool {
///ARC下會(huì)自動(dòng)加入 autorelease方法
NSObject * obj = [[NSObject alloc] init];
///MRC寫(xiě)法
// NSObject * obj = [[[NSObject alloc] init] autorelease];
}
}
進(jìn)入終端,cd到main.m文件目錄下,運(yùn)行以下命令,將文件轉(zhuǎn)換為main.cpp文件查看底層調(diào)用方法:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
在生成的main.cpp文件中我們可發(fā)現(xiàn)main函數(shù)被轉(zhuǎn)化成下面C代碼:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; ///調(diào)用了objc_autoreleasePoolPush
NSObject * obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
}
查看__AtAutoreleasePool發(fā)現(xiàn)是一個(gè)C的結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
這里面主要包含了兩個(gè)方法:
1.__AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); }:構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體時(shí)調(diào)用
2.~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); }:析構(gòu)函數(shù),在銷(xiāo)毀結(jié)構(gòu)體時(shí)調(diào)用
- 為了方便,將代碼轉(zhuǎn)換為以下偽代碼:
int main (int argc, char * argv[]) {
// push
void *poolToken = objc_autoreleasePoolPush();
這中間為寫(xiě)在{...}中的代碼
// pop 將{...}中的對(duì)象都執(zhí)行一次 release操作
objc_autoreleasePoolPop(哨兵對(duì)象地址);//哨兵對(duì)象后面會(huì)講到
}
-上面提到objc_autoreleasePoolPush()和 objc_autoreleasePoolPop(atautoreleasepoolobj)兩個(gè)方法,看看源碼實(shí)現(xiàn)(只保留重要部分):
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
由此發(fā)現(xiàn)autoreleasePool是依賴(lài)AutoreleasePoolPage實(shí)現(xiàn)的,具體里面實(shí)現(xiàn)后面再說(shuō),先來(lái)看看AutoreleasePoolPage的結(jié)構(gòu)。
3.autoreleasePoolPage的結(jié)構(gòu)
以下為AutoreleasePoolPage結(jié)構(gòu)的主要部分
class AutoreleasePoolPage
{
PAGE_MAX_SIZE;//最大size 4096字節(jié)
magic_t const magic; //用來(lái)校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
id *next;//指向下一個(gè)即將產(chǎn)生的autoreleased對(duì)象的存放位置(當(dāng)next == begin()時(shí),表示AutoreleasePoolPage為空;當(dāng)next == end()時(shí),表示AutoreleasePoolPage已滿(mǎn)
pthread_t const thread;//指向當(dāng)前線程,一個(gè)AutoreleasePoolPage只會(huì)對(duì)應(yīng)一個(gè)線程,但一個(gè)線程可以對(duì)應(yīng)多個(gè)AutoreleasePoolPage;
AutoreleasePoolPage * const parent;//指向父結(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil;
AutoreleasePoolPage *child;//指向子結(jié)點(diǎn),最后一個(gè)結(jié)點(diǎn)的 child 值為 nil;
uint32_t const depth;//代表深度,第一個(gè)page的depth為0,往后每遞增一個(gè)page,depth會(huì)加1;
}
如下圖:

- 1.AutoreleasePoolPage 本質(zhì)是這么一個(gè)節(jié)點(diǎn)對(duì)象,大小是4096字(PAGE_MAX_SIZE:4096)。
- 2.前7個(gè)變量都是8字節(jié),剩下的4040字節(jié)存儲(chǔ)著autorelease對(duì)象地址
- push的調(diào)用分析
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
static inline void *push()
{
return autoreleaseFast(POOL_BOUNDARY);
}
static inline id *autoreleaseFast(id obj)
{
//hotPage()表示當(dāng)前頁(yè)的 AutoreleasePoolPage 節(jié)點(diǎn)
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
// 當(dāng)前 page 存在且沒(méi)有滿(mǎn)時(shí),直接將對(duì)象添加到當(dāng)前 page 中,即 next 指向的位置
return page->add(obj);
} else if (page) {
// 當(dāng)前 page 存在且已滿(mǎn)時(shí),創(chuàng)建一個(gè)新的 page ,并將對(duì)象添加到新創(chuàng)建的 page 中
return autoreleaseFullPage(obj, page);
} else {
// 當(dāng)前 page 不存在時(shí),即還沒(méi)有 page 時(shí),創(chuàng)建第一個(gè) page ,并將對(duì)象添加到新創(chuàng)建的 page 中
return autoreleaseNoPage(obj);
}
}
[obj autorelease], 給對(duì)象添加 autorelease 方法, 其實(shí)內(nèi)部就是直接調(diào)用了 autoreleaseFast
4.autoreleasePool的結(jié)構(gòu)和工作原理
-
autoreleasepool本質(zhì)上就是一個(gè)指針堆棧,內(nèi)部結(jié)構(gòu)是由若干個(gè)以AutoreleasePoolPage對(duì)象為結(jié)點(diǎn)的雙向鏈表組成,系統(tǒng)會(huì)在需要的時(shí)候動(dòng)態(tài)地增加或刪除page節(jié)點(diǎn),如下圖即為AutoreleasePoolPage組成的雙向鏈表:
截屏2021-03-05 下午4.39.46.png - 參考上圖,整個(gè)流程大概如下:
1.在運(yùn)行循環(huán)開(kāi)始前,系統(tǒng)會(huì)自動(dòng)創(chuàng)建一個(gè)autoreleasepool(一個(gè)autoreleasepool會(huì)存在多個(gè)AutoreleasePoolPage),此時(shí)會(huì)調(diào)用一次objc_autoreleasePoolPush函數(shù),runtime會(huì)向當(dāng)前的AutoreleasePoolPage中add進(jìn)一個(gè)POOL_BOUNDARY(哨兵對(duì)象),代表autoreleasepool的起始邊界地址),并返回此哨兵對(duì)象的內(nèi)存地址。
2.這時(shí)候next指針則會(huì)指向POOL_BOUNDARY(哨兵對(duì)象)后面的地址(對(duì)象地址1)。
3.后面我們創(chuàng)建對(duì)象,如果對(duì)象調(diào)用了autorelease方法(ARC編譯器會(huì)給對(duì)象自動(dòng)插入autorelease),則會(huì)被添加進(jìn)AutoreleasePoolPage中,位置是在next指針指向的位置,如上面next指向的是對(duì)象地址1,這是后添加的對(duì)象地址就在對(duì)象地址1這里,然后next就會(huì) 指向到對(duì)象地址2 ,以此類(lèi)推,每添加一個(gè)地址就會(huì)向前移動(dòng)一次,直到指向end()表示已存滿(mǎn)。
4.當(dāng)不斷的創(chuàng)建對(duì)象時(shí),AutoreleasePoolPage不斷存儲(chǔ)對(duì)象地址,直到存滿(mǎn)后,則又會(huì)創(chuàng)建一個(gè)新的AutoreleasePoolPage,使用child指針和parent指針指向下一個(gè)和上一個(gè)page,從而形成一個(gè)雙向鏈表,對(duì)象地址存儲(chǔ)的順序如圖所示。
5.當(dāng)調(diào)用objc_autoreleasePoolPop(哨兵對(duì)象地址)時(shí)(調(diào)用時(shí)機(jī)后面說(shuō)),假設(shè)我們?nèi)缟蠄D,添加最后一個(gè)對(duì)象地址8,那么這時(shí)候就會(huì)依次由對(duì)象地址8 -> 對(duì)象地址1,每個(gè)對(duì)象都會(huì)調(diào)用release方法釋放,直到遇到哨兵對(duì)象地址為止。
5.autoreleasepool的嵌套
當(dāng)多個(gè)autoreleasepool嵌套,對(duì)象的釋放,會(huì)是什么情況呢?
每次新建一個(gè)@ autoreleasepool,就會(huì)執(zhí)行一次push操作,對(duì)應(yīng)的具體實(shí)現(xiàn)就是往AutoreleasePoolPage中的next位置插入一個(gè)POOL_BOUNDARY(哨兵對(duì)象)。
如下:
@autoreleasepool {//autoreleasepool1
NSObject * obj1 = [[NSObject alloc] init];
@autoreleasepool {//autoreleasepool2
NSObject * obj2 = [[NSObject alloc] init];
NSObject * obj3 = [[NSObject alloc] init];
}
}

釋放流程:
1.當(dāng)autoreleasepool1創(chuàng)建時(shí),會(huì)添加哨兵對(duì)象1,接著obj1的創(chuàng)建,則把obj1地址添加進(jìn)來(lái)。
- 當(dāng)autoreleasepool2創(chuàng)建,會(huì)添加哨兵對(duì)象2,位置是obj1后面(上面next指針指向原理),然后依次把obj2和obj3加進(jìn)來(lái)。
3.當(dāng)autoreleasepool2結(jié)束時(shí),obj3,obj2,會(huì)找到離它們最近的autoreleasepool即
autoreleasepool2,然后依次調(diào)用release,直到哨兵對(duì)象2位置。
4.當(dāng)autoreleasepool1結(jié)束時(shí),當(dāng)obj1調(diào)用release,直到哨兵對(duì)象1位置,
6. autorelaeasepool、NSRunLoop 、子線程三者的關(guān)系
1.主線程默認(rèn)為我們開(kāi)啟 Runloop,Runloop 會(huì)自動(dòng)幫我們創(chuàng)建Autoreleasepool,并進(jìn)行Push、Pop 等操作來(lái)進(jìn)行內(nèi)存管理。
2.子線程默認(rèn)不開(kāi)啟runloop,當(dāng)產(chǎn)生autorelease對(duì)象時(shí)候,會(huì)將對(duì)象添加到最近一次創(chuàng)建的autoreleasepool中,一般是main函數(shù)中的autoreleasepool,由主線程runloop管理;也就是不用手動(dòng)創(chuàng)建Autoreleasepool,線程銷(xiāo)毀時(shí)在會(huì)在最近一次創(chuàng)建的autoreleasepool 中釋放對(duì)象。
3.自定義的 NSOperation 和 NSThread 需要手動(dòng)創(chuàng)建自動(dòng)釋放池。比如: 自定義的 NSOperation 類(lèi)中的 main 方法里就必須添加自動(dòng)釋放池。否則出了作用域后,自動(dòng)釋放對(duì)象會(huì)因?yàn)闆](méi)有自動(dòng)釋放池去處理它,而造成內(nèi)存泄露。
但對(duì)于 blockOperation 和 invocationOperation 這種默認(rèn)的Operation ,系統(tǒng)已經(jīng)幫我們封裝好了,不需要手動(dòng)創(chuàng)建自動(dòng)釋放池。
4.AutoreleasePool是按線程一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程),每開(kāi)一個(gè)線程,會(huì)有與之對(duì)應(yīng)的AutoreleasePool。
點(diǎn)個(gè)贊再走唄~
