<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)的類
- AutoreleasePool并沒有單獨的結(jié)構(gòu)蹬音,而是由若干個AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)
- 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)存如下圖:
圖中的情況懦砂,這一頁再加入一個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就變成了下面的樣子:
objc_autoreleasePoolPush
的返回值正是這個哨兵對象的地址汇在,被objc_autoreleasePoolPop
(哨兵對象)作為入?yún)⒑踩谑牵?/p>
根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
在當(dāng)前page
中,將晚于哨兵對象插入的所有autorelease
對象都發(fā)送一次- release
消息糕殉,并向回移動next指針到正確位置
補(bǔ)充2:從最新加入的對象一直向前清理缨历,可以向前跨越若干個page
,直到哨兵所在的page
剛才的objc_autoreleasePoolPop
執(zhí)行后糙麦,最終變成了下面的樣子:
知道了上面的原理辛孵,嵌套的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)識,再添加灸眼。卧檐。。焰宣。