Autorelease介紹
Autorelease機(jī)制是iOS開發(fā)者管理對象內(nèi)存的好伙伴,MRC中,調(diào)用[obj autorelease]來延遲內(nèi)存的釋放是一件簡單自然的事,ARC下,我們甚至可以完全不知道Autorelease就能管理好內(nèi)存。而在這背后,objc和編譯器都幫我們做了哪些事呢,它們是如何協(xié)作來正確管理內(nèi)存的呢?
Autorelease的使用
// 創(chuàng)建一個(gè)自動(dòng)釋放池
@autoreleasepool {
Person *person = [[[Person alloc] init] autorelease];
}
// 轉(zhuǎn)成C++的代碼如下
{ __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
// __AtAutoreleasePool結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
// 哨兵對象
void * atautoreleasepoolobj;
};
// 簡化C++代碼 如下
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
那么問題來了這三行代碼做了什么,讓我們來看一下runtime的源碼實(shí)現(xiàn)
// 先來看一下第一行代碼
atautoreleasepoolobj = objc_autoreleasePoolPush();
// runtime實(shí)現(xiàn)
void *objc_autoreleasePoolPush(void)
{
// 那AutoreleasePoolPage這個(gè)東東是個(gè)什么玩意兒里 我們稍后說
return AutoreleasePoolPage::push();
}
// 來看一下二行,第二行簡單的說就是Person實(shí)例對象調(diào)用了一下Autorelease方法,來看一下Autorelease方法
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
// 經(jīng)過連續(xù)調(diào)用最終來到
__attribute__((noinline,used)) id objc_object::rootAutorelease2()
{
// 判斷是否是TaggedPointer 這個(gè)是對NSNumber 、NSString、NSDate等對象的內(nèi)存優(yōu)化
assert(!isTaggedPointer());
// 把當(dāng)前對象傳進(jìn)去 。又見到了AutoreleasePoolPage這個(gè)東東
return AutoreleasePoolPage::autorelease((id)this);
}
// 再看一下第三行
void objc_autoreleasePoolPop(void *ctxt)
{
// 又是AutoreleasePoolPage這個(gè)東東
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage這個(gè)是個(gè)什么東西呢?
// 一個(gè)C++實(shí)現(xiàn)類
class AutoreleasePoolPage {
// 省略部分代碼
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
static size_t const COUNT = SIZE / sizeof(id); // 大小 4096字節(jié)
magic_t const magic;
id *next; // 最后一個(gè)Autorelease對象
pthread_t const thread; // 對應(yīng)的線程
AutoreleasePoolPage * const parent; // 上一頁(AutoreleasePoolPag對象)
AutoreleasePoolPage *child; // 下一頁(AutoreleasePoolPag對象)
uint32_t const depth;
uint32_t hiwat;
// 省略部分代碼
}
// AutoreleasePoolPage
1. AutoreleasePool并沒有單獨(dú)的結(jié)構(gòu),而是由若干個(gè)AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)
2. AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
3. AutoreleasePoolPage每個(gè)對象會(huì)開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大?。松厦娴膶?shí)例變量所占空間,剩下的空間全部用來儲(chǔ)存autorelease對象的地址
4. 上面的id *next指針作為游標(biāo)指向棧頂最新add進(jìn)來的autorelease對象的下一個(gè)位置
5. 一個(gè)AutoreleasePoolPage的空間被占滿時(shí),會(huì)新建一個(gè)AutoreleasePoolPage對象,連接鏈表,后來的autorelease對象在新的page加入

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

objc_autoreleasePoolPush的返回值正是這個(gè)哨兵對象的地址,被objc_autoreleasePoolPop(哨兵對象)作為入?yún)?,于是?/p>
- 根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
- 在當(dāng)前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次
- release消息,并向回移動(dòng)next指針到正確位置 - 補(bǔ)充2:從最新加入的對象一直向前清理,可以向前跨越若干個(gè)page,直到哨兵所在的page
剛才的objc_autoreleasePoolPop執(zhí)行后,最終變成了下面的樣子:

Runloop和Autorelease
iOS在主線程的Runloop中注冊了2個(gè)Observer
第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
第2個(gè)Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件,會(huì)調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
監(jiān)聽了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()
Autorelease什么時(shí)候釋放
1.在沒有手加Autorelease Pool的情況下,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時(shí)釋放的,而它能夠釋放的原因是系統(tǒng)在每個(gè)runloop迭代中都加入了自動(dòng)釋放池Push和Pop
2.有手動(dòng)加Autorelease Pool的情況下出了作用域就會(huì)調(diào)用自動(dòng)釋放池Pop對每個(gè)對象調(diào)用release