淺析autoreleasePool

<font color = 'gray'>2018-10-26 編輯 :yzl </font>

Autorelease對象什么時候釋放粒梦?

在沒有手加Autorelease Pool的情況下荸实,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時釋放的,而它能夠釋放的原因是系統(tǒng)在每個runloop迭代中都加入了自動釋放池Push和Pop昼蛀。

實驗

__weak id reference = nil;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    // str是一個autorelease對象圆存,設(shè)置一個weak的引用來觀察它
    reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%@", reference); // Console: sunnyxx
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    NSLog(@"%@", reference); // Console: (null)
}

這個實驗同時也證明了viewDidLoad和viewWillAppear是在同一個runloop調(diào)用的,而viewDidAppear是在之后的某個runloop調(diào)用的夫植。
由于這個vc在loadView之后便add到了window層級上,所以viewDidLoad和viewWillAppear是在同一個runloop調(diào)用的延欠,因此在viewWillAppear中沈跨,這個autorelease的變量依然有值。

當(dāng)然狞玛,我們也可以手動干預(yù)Autorelease對象的釋放時機(jī):

- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    }
    NSLog(@"%@", str); // Console: (null)
}

Autorelease原理

int main(int argc, char * argv[]) {
    @autoreleasepool {

    }
}

clang之后

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;


    }
}

也就是說 @autoreleasepool {} 被轉(zhuǎn)換為一個 __AtAutoreleasePool 結(jié)構(gòu)體:

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

這個結(jié)構(gòu)體會在初始化時調(diào)用 objc_autoreleasePoolPush() 方法心肪,會在析構(gòu)時調(diào)用 objc_autoreleasePoolPop 方法纠吴。

這表明,我們的 main 函數(shù)在實際工作時其實是這樣的:

void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);

objc_autoreleasePoolPush 和 objc_autoreleasePoolPop 的實現(xiàn):

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

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

而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝固该,所以自動釋放機(jī)制的核心就在于這個類恭陡。

AutoreleasePoolPage是一個C++實現(xiàn)的類


51530583gw1elj2ugt21wj20f109m3zl.jpg
  • AutoreleasePool并沒有單獨的結(jié)構(gòu)蹬音,而是由若干個AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)
1975281-0b868956aa0cf87c.png
  • AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
  • AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大猩厦骸)休玩,除了上面的實例變量所占空間,剩下的空間全部用來儲存autorelease對象的地址
  • 上面的id *next指針作為游標(biāo)指向棧頂最新add進(jìn)來的autorelease對象的下一個位置
  • 一個AutoreleasePoolPage的空間被占滿時劫狠,會新建一個AutoreleasePoolPage對象拴疤,連接鏈表,后來的autorelease對象在新的page加入

所以独泞,若當(dāng)前線程中只有一個AutoreleasePoolPage對象呐矾,并記錄了很多autorelease對象地址時內(nèi)存如下圖:


51530583gw1elj5gvphtqj20dy0cx756.jpg

圖中的情況懦砂,這一頁再加入一個autorelease對象就要滿了(也就是next指針馬上指向棧頂)蜒犯,這時就要執(zhí)行上面說的操作,建立下一頁page對象荞膘,與這一頁鏈表連接完成后罚随,新page的next指針被初始化在棧底(begin的位置),然后繼續(xù)向棧頂添加新對象羽资。

所以淘菩,向一個對象發(fā)送- autorelease消息,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置屠升。

每當(dāng)進(jìn)行一次objc_autoreleasePoolPush調(diào)用時潮改,runtime向當(dāng)前的AutoreleasePoolPage中add進(jìn)一個哨兵對象狭郑,值為0(也就是個nil),那么這一個page就變成了下面的樣子:


51530583gw1elj5z7hawej20ji0dewff.jpg

objc_autoreleasePoolPush的返回值正是這個哨兵對象的地址汇在,被objc_autoreleasePoolPop(哨兵對象)作為入?yún)⒑踩谑牵?/p>

根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
在當(dāng)前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息糕殉,并向回移動next指針到正確位置
補(bǔ)充2:從最新加入的對象一直向前清理缨历,可以向前跨越若干個page,直到哨兵所在的page
剛才的objc_autoreleasePoolPop執(zhí)行后糙麦,最終變成了下面的樣子:

51530583gw1elj6u2i3fyj20dz0bqdgi.jpg

知道了上面的原理辛孵,嵌套的AutoreleasePool就非常簡單了,pop的時候總會釋放到上次push的位置為止赡磅,多層的pool就是多個哨兵對象而已魄缚,就像剝洋蔥一樣,每次一層焚廊,互不影響冶匹。

objc_autoreleasePoolPush

objc_autoreleasePoolPush 方法:

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

它調(diào)用 AutoreleasePoolPage 的類方法 push,也非常簡單:

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}

在這里會進(jìn)入一個比較關(guān)鍵的方法 autoreleaseFast咆瘟,并傳入哨兵對象 POOL_SENTINEL:

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

上述方法分三種情況選擇不同的代碼執(zhí)行:

  • 有 hotPage 并且當(dāng)前 page 不滿
  • 調(diào)用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
  • 有 hotPage 并且當(dāng)前 page 已滿
  • 調(diào)用 autoreleaseFullPage 初始化一個新的頁
  • 調(diào)用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
  • 無 hotPage
  • 調(diào)用 autoreleaseNoPage 創(chuàng)建一個 hotPage
  • 調(diào)用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
  • 最后的都會調(diào)用 page->add(obj) 將對象添加到自動釋放池中嚼隘。

當(dāng)obj 調(diào)用autoRelease方法時,其實就是調(diào)用autoreleaseFast(obj)袒餐;

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

結(jié)束

本文暫時分析到此飞蛹,后續(xù)有新認(rèn)識,再添加灸眼。卧檐。。焰宣。

參考文獻(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霉囚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子匕积,更是在濱河造成了極大的恐慌盈罐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪唆,死亡現(xiàn)場離奇詭異盅粪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苞氮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門湾揽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事库物“云欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵戚揭,是天一觀的道長诱告。 經(jīng)常有香客問我,道長民晒,這世上最難降的妖魔是什么精居? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮潜必,結(jié)果婚禮上靴姿,老公的妹妹穿的比我還像新娘。我一直安慰自己磁滚,他們只是感情好佛吓,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垂攘,像睡著了一般维雇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晒他,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天吱型,我揣著相機(jī)與錄音,去河邊找鬼陨仅。 笑死津滞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掂名。 我是一名探鬼主播据沈,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饺蔑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗜诀,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猾警,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后隆敢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體发皿,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年拂蝎,在試婚紗的時候發(fā)現(xiàn)自己被綠了穴墅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玄货,靈堂內(nèi)的尸體忽然破棺而出皇钞,到底是詐尸還是另有隱情,我是刑警寧澤松捉,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布夹界,位于F島的核電站,受9級特大地震影響隘世,放射性物質(zhì)發(fā)生泄漏可柿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一丙者、第九天 我趴在偏房一處隱蔽的房頂上張望复斥。 院中可真熱鬧,春花似錦械媒、人聲如沸永票。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侣集。三九已至,卻和暖如春兰绣,著一層夾襖步出監(jiān)牢的瞬間世分,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工缀辩, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留臭埋,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓臀玄,卻偏偏與公主長得像瓢阴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子健无,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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