Autorelease

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加入

AutoreleasePoolPage.png

釋放時(shí)刻

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

image

objc_autoreleasePoolPush的返回值正是這個(gè)哨兵對象的地址,被objc_autoreleasePoolPop(哨兵對象)作為入?yún)?,于是?/p>

  1. 根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
  2. 在當(dāng)前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息,并向回移動(dòng)next指針到正確位置
  3. 補(bǔ)充2:從最新加入的對象一直向前清理,可以向前跨越若干個(gè)page,直到哨兵所在的page

剛才的objc_autoreleasePoolPop執(zhí)行后,最終變成了下面的樣子:

image

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

參考地址
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/

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

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