iOS autoreleasePool原理總結(jié)

目錄

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

如下圖:


AutoreleasePoolPage結(jié)構(gòu)圖.png
  • 1.AutoreleasePoolPage 本質(zhì)是這么一個(gè)節(jié)點(diǎn)對(duì)象,大小是4096字(PAGE_MAX_SIZE:4096)。
  • 2.前7個(gè)變量都是8字節(jié),剩下的4040字節(jié)存儲(chǔ)著autorelease對(duì)象地址
    1. 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];
    }
}
autoreleasepool的嵌套.png

釋放流程:
1.當(dāng)autoreleasepool1創(chuàng)建時(shí),會(huì)添加哨兵對(duì)象1,接著obj1的創(chuàng)建,則把obj1地址添加進(jìn)來(lái)。

  1. 當(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è)贊再走唄~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容