iOS 底層 - 內(nèi)存管理之自動(dòng)釋放池與RunLoop

本文源自本人的學(xué)習(xí)記錄整理與理解,其中參考閱讀了部分優(yōu)秀的博客和書(shū)籍,盡量以通俗簡(jiǎn)單的語(yǔ)句轉(zhuǎn)述。引用到的地方如有遺漏或未能一一列舉原文出處還望見(jiàn)諒與指出,另文章內(nèi)容如有不妥之處還望指教,萬(wàn)分感謝 !

緣分一道橋.png

自動(dòng)釋放池是什么嘞 ?

自動(dòng)釋放池:即@autoreleasepool,通過(guò)AutoreleasePoolPage來(lái)管理調(diào)用了autorelease方法的對(duì)象,把該對(duì)象在合適的時(shí)機(jī)釋放掉,這就是自動(dòng)釋放池

自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage

release 和 drain的區(qū)別

當(dāng)我們向自動(dòng)釋放池 pool 發(fā)送 release 消息,將會(huì)向池中臨時(shí)對(duì)象發(fā)送一條 release 消息,并且自身也會(huì)被銷毀
向它自動(dòng)釋放池發(fā)送drain消息時(shí),將會(huì)向池中臨時(shí)對(duì)象發(fā)送一條release消息同時(shí)在支持 GC 的系統(tǒng)中(Mac)可以引起 GC 回收操作,而 release 不可以

官方解釋
release:清空自動(dòng)釋放池且把自動(dòng)釋放池對(duì)象釋放。(ARC)
drain:清空自動(dòng)釋放池

  • 原理:

define POOL_BOUNDARY nil ;nil值為0 ,是一個(gè)哨兵對(duì)象

  • 假設(shè)還沒(méi)有創(chuàng)建AutoreleasePoolPage,從第一個(gè)autorelease對(duì)象開(kāi)始

  • 調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧(不是棧空間,而是數(shù)據(jù)結(jié)構(gòu)中的棧),并且返回POOL_BOUNDARY存儲(chǔ)的內(nèi)存地址

  • 調(diào)用push方法開(kāi)始存放第一個(gè)autorelease對(duì)象的地址,返回存儲(chǔ)的地址

  • 調(diào)用pop方法時(shí)傳入一個(gè)POOLBOUNDARY的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對(duì)象開(kāi)始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY

  • id *next 是查詢指針,指向下一個(gè)可以用來(lái)存放autorelease對(duì)象地址的區(qū)域,

POOL_BOUNDARY是一個(gè)標(biāo)記,代表一個(gè)自動(dòng)釋放池和另一個(gè)自動(dòng)釋放池的邊界,所以存取都需要

@autoreleasepool 最終會(huì)在大括號(hào)的開(kāi)始和結(jié)束生成以下兩行代碼:

@autoreleasepool {
                      //添加
                     atautoreleasepoolobj = objc_autoreleasePoolPush();

                      實(shí)現(xiàn)了autorelease的對(duì)象

                     //推出
                    objc_autoreleasePoolPop(atautoreleasepoolobj);
          }

直觀的代碼邏輯展示

 @autoreleasepool {
      atautoreleasepoolobj = objc_autoreleasePoolPush();
        
         @autoreleasepool {
                atautoreleasepoolobj = objc_autoreleasePoolPush();
        
                    @autoreleasepool {
                                atautoreleasepoolobj = objc_autoreleasePoolPush();

                                    for (   循環(huán)  )   {  
                                            @autoreleasepool ........
                                          }

                             objc_autoreleasePoolPop(atautoreleasepoolobj);
                          }

              objc_autoreleasePoolPop(atautoreleasepoolobj);
       }
       objc_autoreleasePoolPop(atautoreleasepoolobj);
    }

從runtime源碼中找實(shí)現(xiàn)

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

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

push()


 id *autoreleaseNewPage(id obj)
    {
        //創(chuàng)建
        AutoreleasePoolPage *page = hotPage();
        //把需要加入的對(duì)象添加(page->add(obj))進(jìn)來(lái)
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }

 static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {//當(dāng)前沒(méi)有pool page
            // Each autorelease pool starts on a new pool page.
            //傳入POOL_BOUNDARY,新創(chuàng)建一個(gè)pool page并把obj地址添加進(jìn)去
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {//直接傳入POOL_BOUNDARY,并把obj地址添加進(jìn)去
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

pop()

static inline void
    pop(void *token)//token:POOL_BOUNDARY的地址
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            拿到當(dāng)前頁(yè)釋放池
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            拿到上一頁(yè)釋放池
            page = coldPage();
            獲取第一個(gè)存儲(chǔ)位
            token = page->begin();
        } else {
            傳入POOL_BOUNDARY的地址,找到page
            page = pageForPointer(token);
        }
        
        停止位
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }
        從最后一個(gè)入棧的對(duì)象開(kāi)始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY返回
        return popPage<false>(token, page, stop);
    }

AutoreleasePoolPage結(jié)構(gòu)

PoolPage結(jié)構(gòu)圖.png
  • 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存,除了用來(lái)存儲(chǔ)它內(nèi)部成員變量占用56字節(jié)外,剩下的空間用來(lái)存放autorelease對(duì)象的地址
    如果內(nèi)存不夠了,就會(huì)再創(chuàng)建一個(gè)新的AutoreleasePoolPage對(duì)象接著存

  • 所有的AutoreleasePoolPage對(duì)象是通過(guò)雙向鏈表的形式連接在一起,第一頁(yè)的上一頁(yè)是0,最后一頁(yè)的下一頁(yè)是0

雙向鏈表:第一個(gè)對(duì)象可以通過(guò)指針一個(gè)一個(gè)找到最后一個(gè)對(duì)象,最后一個(gè)對(duì)象也可以通過(guò)指針一個(gè)一個(gè)找到第一個(gè)對(duì)象,這種結(jié)構(gòu)的鏈表被認(rèn)為是雙向鏈表;

緣分一道橋設(shè)計(jì)思路:

一條鎖鏈搭建的吊橋,兩條粗壯的鎖鏈;假設(shè)建造過(guò)程是這樣的:一條從第一塊橋板的左邊穿過(guò),一直到最后一塊木板到達(dá)對(duì)岸,另一條從最后一塊橋板開(kāi)始往回穿過(guò),一直到回到第一塊橋板的右邊;這個(gè)過(guò)程不就像極了雙向鏈表嘛 ????????????????

雙向鏈表.png

自動(dòng)釋放池與RunLoop有啥關(guān)系 ?

先看看主線程的Runloop做了什么

RunLoop活動(dòng)狀態(tài)

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
 kCFRunLoopEntry = (1UL << 0),            ==  1
 kCFRunLoopBeforeTimers = (1UL << 1),  == 2
 kCFRunLoopBeforeSources = (1UL << 2), == 4
 kCFRunLoopBeforeWaiting = (1UL << 5), == 32
 kCFRunLoopAfterWaiting = (1UL << 6), == 64
 kCFRunLoopExit = (1UL << 7), == 128
 kCFRunLoopAllActivities = 0x0FFFFFFFU
 };
 */


/*

 kCFRunLoopEntry  push
 
 <CFRunLoopObserver>{
valid = Yes, 
activities = 0x1, 活動(dòng)狀態(tài)值 0x1  = 1 --> kCFRunLoopEntry
repeats = Yes, 
order = -2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (t0 : <0x7fd0bf802048>)}
}
 
 
 kCFRunLoopBeforeWaiting | kCFRunLoopExit
 kCFRunLoopBeforeWaiting pop、push
 kCFRunLoopExit pop
 
 <CFRunLoopObserver>{
valid = Yes, 
活動(dòng)狀態(tài)值 0xa0 == 160 = 32 + 128 ;  -->  kCFRunLoopBeforeWaiting | kCFRunLoopExit
activities = 0xa0, 
repeats = Yes, 
order = 2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = {type = mutable-small, count = 1, values = (t0 : <0x7fd0bf802048>)
}
}

梳理一下:

activities = 0x1
活動(dòng)狀態(tài)值 0x1 = 1 --> kCFRunLoopEntry
activities= 0xa0
活動(dòng)狀態(tài)值 0xa0 == 160 = 32 + 128 ; --> kCFRunLoopBeforeWaiting | kCFRunLoopExit

kCFRunLoopEntry: 開(kāi)始進(jìn)入runloop
kCFRunLoopBeforeWaiting:runloop開(kāi)始休眠
kCFRunLoopExit:runloop退出

  • 主線程的runloop中注冊(cè)了2個(gè)Observer(監(jiān)聽(tīng)器)
  • 第1個(gè)Observer:
    • 監(jiān)聽(tīng)kCFRunLoopEntry事件,觸發(fā)就會(huì)調(diào)用objc_autoreleasePoolPush()函數(shù)
  • 第2個(gè)Observer:
    • 監(jiān)聽(tīng)kCFRunLoopBeforeWaiting事件,觸發(fā)就會(huì)調(diào)用objc_autoreleasePoolPop()和objc_autoreleasePoolPush()函數(shù)
    • 監(jiān)聽(tīng)kCFRunLoopExit事件,觸發(fā)就會(huì)調(diào)用objc_autoreleasePoolPop()

看一行代碼來(lái)對(duì)其進(jìn)行分析

XYHPerson *person = [[[XYHPerson alloc] init] autorelease];
  • 這個(gè)Person什么時(shí)候調(diào)用release,是由RunLoop來(lái)控制的
  • 它可能是在某次RunLoop循環(huán)中 ,在RunLoop休眠或退出之前調(diào)用了release

開(kāi)發(fā)中實(shí)現(xiàn)的對(duì)象調(diào)用atuorelease方法會(huì)加入到main函數(shù)中的@atuoreleasepool嗎 ?

不會(huì),main函數(shù)中@atuoreleasepool是專門(mén)為main函數(shù)使用的;不會(huì)用作其他用途;

開(kāi)發(fā)中實(shí)現(xiàn)的對(duì)象調(diào)用atuorelease方法只會(huì)加入當(dāng)前自動(dòng)釋放池,如果當(dāng)前沒(méi)有自動(dòng)釋放池,會(huì)新創(chuàng)建一個(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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