iOS 自動釋放池原理

簡介

自動釋放池(autoreleasepool)是OC的一種內(nèi)存自動回收機制珍昨。正常情況下,創(chuàng)建的變量超出作用域時釋放,自動釋放池可以延遲對象的釋放强重。

原理

OC代碼
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

使用clang命令將OC代碼重寫成C++

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc  main.m
C++代碼
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__AtAutoreleasePool實際是一個結構體绞呈,內(nèi)部首先執(zhí)行objc_autoreleasePoolPush(),然后在調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj)

struct __AtAutoreleasePool {
 **構造函數(shù)间景,在創(chuàng)建結構體時調(diào)用**
  __AtAutoreleasePool() {
  atautoreleasepoolobj = objc_autoreleasePoolPush();
}

**析構函數(shù)佃声,在結構體銷毀的時候調(diào)用**
  ~__AtAutoreleasePool() {
  objc_autoreleasePoolPop(atautoreleasepoolobj);
}
  void * atautoreleasepoolobj;
};
objc4-818源碼

源碼中可以看出,objc_autoreleasePoolPush()函數(shù)內(nèi)調(diào)用了AutoreleasePoolPagepush()方法倘要,objc_autoreleasePoolPop()則調(diào)用了AutoreleasePoolPagepop()方法圾亏。
也就是說,需要Autorelease的對象封拧,都是由AutoreleasePoolPage對象來管理的志鹃。

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

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

AutoreleasePoolPage對象源碼:

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

    ////用來校驗AutoreleasePoolPage的結構是否完整
    magic_t const magic; //16字節(jié)
    //下次新添加的autoreleased對象的位置,初始化時指向begin()
    __unsafe_unretained id *next;//8字節(jié)
    //當前線程
    pthread_t const thread;//8字節(jié)
    //指向父節(jié)點泽西,即上一個頁面曹铃,第一個頁面的parent值為nil
    AutoreleasePoolPage * const parent;//8字節(jié)
    //指向子節(jié)點,即下一個頁面尝苇,最后一個頁面的child值為nil
    AutoreleasePoolPage *child;//8字節(jié)
    //表示頁面深度铛只,從0開始,往后遞增1
    uint32_t const depth; //4字節(jié)
    //high water mark糠溜,表示最大入棧數(shù)量標記
    uint32_t hiwat;
 //初始化
    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

一個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存淳玩,除了存放自己內(nèi)部變量以外,剩下的內(nèi)存空間就用來存放需要Autorelease的對象地址非竿。
所有AutoreleasePoolPage對象都是以棧為節(jié)點通過雙向鏈表的形式連接在一起

AutoreleasePool是由多個AutoreleasePoolPage對象組成的蜕着,以雙向鏈表組成,其中parent指針指向上一個AutoreleasePoolPage對象红柱,child指針指向下一個AutoreleasePoolPage對象承匣,如下圖:

AutoreleasePoolPage管理Autorelease對象的過程
  • AutoreleasePoolPage對象中的next指針指向下一個能存放Autorelease對象地址的區(qū)域。

  • 上文講到锤悄,push()調(diào)用時韧骗,內(nèi)部會將一個POOL_BOUNDARY哨兵入棧,并返回其存放的內(nèi)存地址零聚,然后next指針指向POOL_BOUNDARY后面第一個內(nèi)存地址袍暴。(POOL_BOUNDARY作用應該是起到標記的作用)

  • 當有Autorelease對象入棧時,會存放在next指針指向的內(nèi)存空間隶症,然后next指針指向Autorelease對象地址后面的內(nèi)存空間
    如下圖:

  • 當前的AutoreleasePoolPage對象存放滿了政模,就會創(chuàng)建新的AutoreleasePoolPage對象,用來存放Autorelease對象蚂会。

  • 但自動釋放池結束時淋样,會調(diào)用objc_autoreleasePoolPop()函數(shù),然后調(diào)用AutoreleasePoolPagepop()方法胁住,在pop()方法內(nèi)部會通過next指針找到最后一個入棧的Autorelease對象趁猴,開始發(fā)送release消息進行釋放刊咳,直到找到POOL_BOUNDARY為止,這樣釋放池里面的Autorelease對象就能全部釋放儡司。

Autorelease對象添加過程

當我們調(diào)用autorelease方法時芦缰,底層做了哪些操作

**autorelease 函數(shù)內(nèi)部實現(xiàn)**
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

rootAutorelease內(nèi)部實現(xiàn)

id _objc_rootAutorelease(id obj)
{
    assert(obj);
    return obj->rootAutorelease();
}

inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

rootAutorelease2函數(shù)內(nèi)部實現(xiàn)

id  objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

AutoreleasePoolPage 對象的autorelease 函數(shù)實現(xiàn)

public:
    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

autoreleaseFast 函數(shù)內(nèi)部實現(xiàn)

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

通過源碼可以看到,當調(diào)用autorelease方法時:

  • 底層調(diào)用rootAutorelease函數(shù)枫慷,然后調(diào)用rootAutorelease2函數(shù),然后調(diào)用AutoreleasePoolPage對象的autorelease方法
  • AutoreleasePoolPage對象的autorelease方法調(diào)用autoreleaseFast函數(shù)浪规,函數(shù)內(nèi)將Autorelease對象添加到AutoreleasePoolPage中或听。

自動釋放池釋放時機

系統(tǒng)自動釋放

自動釋放池寄生于Runloop:程序啟動后,主線程會注冊兩個Observer笋婿,回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
1誉裆、監(jiān)測Enter(即將進入Loop)狀態(tài),回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動釋放池缸濒,優(yōu)先級最高足丢。
2、監(jiān)測BeforeWaiting(準備進入休眠)和Exit(即將推出Loop)庇配。BeforeWaiting時調(diào)用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池斩跌;Exit_objc_autoreleasePoolPop() 來釋放自動釋放池;優(yōu)先級最低捞慌。
總結:

  • 程序啟動時耀鸦,Runloop啟動,創(chuàng)建第一個自動釋放池啸澡,事件優(yōu)先級最高袖订。
  • Runloop即將進入休眠時,清理需要釋放的對象嗅虏,調(diào)用pop()洛姑,事件優(yōu)先級最低。
  • Runloop退出時皮服,銷毀最后一個自動釋放池楞艾。
  • Runloop休眠時會釋放舊的并創(chuàng)建新的自動釋放池。
手動釋放

當特定場景時我們自己創(chuàng)建自動釋放池時冰更,在當前作用域大括號結束時釋放产徊。

手動使用AutoreleasePool場景

  • 寫給予命令行的程序時,就是沒有UI框架蜀细;
  • 寫循環(huán)舟铜,循環(huán)里邊包含了大量臨時創(chuàng)建的對象;
  • 創(chuàng)建了新的線程奠衔;
  • 長時間在后臺運行的任務

以上信息只用于本人學習使用谆刨,基本抄的此鏈接中的內(nèi)容塘娶,特此聲明

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痊夭,隨后出現(xiàn)的幾起案子刁岸,更是在濱河造成了極大的恐慌,老刑警劉巖她我,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虹曙,死亡現(xiàn)場離奇詭異,居然都是意外死亡番舆,警方通過查閱死者的電腦和手機酝碳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恨狈,“玉大人疏哗,你說我怎么就攤上這事『痰。” “怎么了返奉?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吗氏。 經(jīng)常有香客問我芽偏,道長,這世上最難降的妖魔是什么弦讽? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任哮针,我火速辦了婚禮,結果婚禮上坦袍,老公的妹妹穿的比我還像新娘十厢。我一直安慰自己,他們只是感情好捂齐,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布蛮放。 她就那樣靜靜地躺著,像睡著了一般奠宜。 火紅的嫁衣襯著肌膚如雪包颁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天压真,我揣著相機與錄音娩嚼,去河邊找鬼。 笑死滴肿,一個胖子當著我的面吹牛岳悟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贵少,長吁一口氣:“原來是場噩夢啊……” “哼呵俏!你這毒婦竟也來了?” 一聲冷哼從身側響起滔灶,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤普碎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后录平,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麻车,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年斗这,在試婚紗的時候發(fā)現(xiàn)自己被綠了绪氛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡涝影,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出争占,到底是詐尸還是另有隱情燃逻,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布臂痕,位于F島的核電站伯襟,受9級特大地震影響,放射性物質發(fā)生泄漏握童。R本人自食惡果不足惜姆怪,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望澡绩。 院中可真熱鬧稽揭,春花似錦、人聲如沸肥卡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽步鉴。三九已至揪胃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氛琢,已是汗流浹背喊递。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阳似,地道東北人骚勘。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像撮奏,于是被迫代替她去往敵國和親调鲸。 傳聞我的和親對象是個殘疾皇子盛杰,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

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