iOS-- autoreleasePool(自動(dòng)釋放池)

手動(dòng)目錄

  • 初探autoreleasePool結(jié)構(gòu)
  • AutoreleasePoolPage 結(jié)構(gòu)
  • 每個(gè)Page能裝多少個(gè)對(duì)象
  • push & pop
  • 面試題

初探autoreleasePool結(jié)構(gòu)

用clang的方式探索autoreleasePool

int main(int argc, char * argv[]){
    @autoreleasepool {
        
    }
    return 1;
}

用clang命令:clang -rewrite-objc main.m 之后得到.cpp文件

int main(int argc, char * argv[]){
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return 1;
}

關(guān)鍵就在{ __AtAutoreleasePool __autoreleasepool; } ,在.cpp 文件中 找到其結(jié)構(gòu)

// 是一個(gè)結(jié)構(gòu)體
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}      // 構(gòu)造函數(shù)
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}         // 析構(gòu)函數(shù)
  void * atautoreleasepoolobj;
};

在這個(gè)結(jié)構(gòu)體中,它調(diào)用了一個(gè)objc_autoreleasePoolPush方法 ,那么它到底干了什么,我們?cè)趏bjc源碼中去看

objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();          // 壓棧
}

class AutoreleasePoolPage : private AutoreleasePoolPageData

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

找到這里的時(shí)候,我們先來看看AutoreleasePoolPage的說明

   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 

說明里面提到幾點(diǎn):
1、stack 先進(jìn)后出
2荐健、pool boundary 邊界(也有叫哨兵的)冠息,它是一個(gè)對(duì)象觉痛,可以理解為標(biāo)記琴儿。
3、釋放池里存的 要么是邊界嘁捷,要么是需要釋放的對(duì)象造成。
4、是一個(gè)雙向鏈表結(jié)構(gòu)

繼續(xù)跟蹤AutoreleasePoolPage的父類AutoreleasePoolPageData

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;                        // 檢查校驗(yàn)完整性的變量                   16位
    __unsafe_unretained id *next;               // 指向新加入的autorelease對(duì)象下一個(gè)節(jié)點(diǎn)   8
    pthread_t const thread;                     // 當(dāng)前所在的線程                        8
    AutoreleasePoolPage * const parent;         // 父節(jié)點(diǎn) 指向前一個(gè)page                  8
    AutoreleasePoolPage *child;                 // 子節(jié)點(diǎn) 指向下一個(gè)page                 8
    uint32_t const depth;                       // 鏈表的深度雄嚣,節(jié)點(diǎn)個(gè)數(shù)                   4
    uint32_t hiwat;                             // 數(shù)據(jù)容納的一個(gè)上限                     4

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

struct magic_t {
    static const uint32_t M0 = 0xA1A1A1A1;              // 全局靜態(tài) 在常量區(qū) .data段  不占這里的位數(shù)
#   define M1 "AUTORELEASE!"
    static const size_t M1_len = 12;                    // 全局靜態(tài) 在常量區(qū) .data段  不占這里的位數(shù)
    uint32_t m[4];                                      // 4 * 4 = 16   這才是為什么這個(gè)結(jié)構(gòu)體16位
    ......
};

相關(guān)屬性說明

  • magic 檢查校驗(yàn)完整性的變量
  • next 指向新加入的autorelease對(duì)象下一個(gè)位置
  • thread page當(dāng)前所在的線程
  • parent 父節(jié)點(diǎn) 指向前一個(gè)page
  • child 子節(jié)點(diǎn) 指向下一個(gè)page
  • depth 鏈表的深度晒屎,節(jié)點(diǎn)個(gè)數(shù)
  • hiwat 數(shù)據(jù)容納的一個(gè)上限

鏈表的結(jié)構(gòu)大概為:


AutoreleasePoolPage

每個(gè)Page能裝多少個(gè)對(duì)象

我們通過源碼來找:

id * begin() {
    return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end() {
    return (id *) ((uint8_t *)this+SIZE);
}

begin 從 sizeof(this))開始 (上面計(jì)算了它的大小 56) 到 SIZE結(jié)束
SIZE = PAGE_MIN_SIZE = PAGE_SIZE = 4096喘蟆;
那么能存的對(duì)象數(shù)量是:
(4096 - 56)/8 = 505*

打印來驗(yàn)證
在可運(yùn)行的objc源碼中, 修改setting -> automatic reference counting = NO

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        for (NSInteger i = 0; i < 5; i ++) {
            NSObject *obj = [[NSObject alloc] autorelease];
        }
        _objc_autoreleasePoolPrint();
        return 0;
    }
}

// 打印結(jié)果
1objc[65796]: ##############
objc[65796]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[65796]: 6 releases pending.                                      // 這里說了  添加了6個(gè)
objc[65796]: [0x10181d000]  ................  PAGE  (hot) (cold)
objc[65796]: [0x10181d038]  ################  POOL 0x10181d038
objc[65796]: [0x10181d040]       0x10103d480  NSObject
....
objc[65796]: ##############

// 上面的循環(huán)換成 1 + 504 
1objc[66085]: ##############
objc[66085]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[66085]: 506 releases pending.                                                // 這里說了 506
objc[66085]: [0x101809000]  ................  PAGE (full)  (cold)      // 注意這里:  page  full(滿了)
objc[66085]: [0x101809038]  ################  POOL 0x101809038
objc[66085]: [0x101809040]       0x10104af30  NSObject
....
objc[66085]: [0x101809ff8]       0x10104f6e0  NSObject
objc[66085]: [0x10180b000]  ................  PAGE  (hot)               // 這里也換了一頁
objc[66085]: [0x10180b038]       0x10104f6f0  NSObject
objc[66085]: ##############
KCObjcTest was compiled with optimization - stepping may behave oddly; variables may not be available.

通過更多的打印鼓鲁,我們發(fā)現(xiàn)

每頁可以添加505個(gè)對(duì)象
但是第一頁實(shí)際只加進(jìn)去504蕴轨,因?yàn)榈谝豁撚幸粋€(gè)邊界對(duì)象
后面的每頁可以實(shí)際加入505個(gè)對(duì)象

push & pop

先看源碼 如何將對(duì)象加入進(jìn)去
在看源碼之前先了解hotPage是當(dāng)前頁 對(duì)看源碼有幫助

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

// push
static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

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

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

后面的 add()  骇吭、autoreleaseFullPage()橙弱、autoreleaseNoPage()可以繼續(xù)往下跟。代碼太長燥狰,就不貼出來了

pop

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

pop(void *token) {}

popPage()

// 具體代碼就不貼了棘脐。

壓棧過程 (push)
1、如果有分頁龙致,并且這個(gè)頁還沒有滿蛀缝,直接add
2、如果有分頁目代,并且滿了屈梁,創(chuàng)建一張新頁便加入對(duì)象。
3榛了、如果還沒有頁在讶,創(chuàng)建一個(gè)page 并加入邊界對(duì)象和要加入的對(duì)象。

出棧(pop)
1忽冻、通過while循環(huán)真朗,從后往前找到所有page的對(duì)象,并進(jìn)行release操作
2僧诚、刪除所有的空表

autoreleasePool 嵌套

嵌套使用autoreleasePool的情況下遮婶,會(huì)發(fā)生什么?
我們用代碼來打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] autorelease];
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] autorelease];
            @autoreleasepool {
                NSObject *obj = [[NSObject alloc] autorelease];
                _objc_autoreleasePoolPrint();
            }
        }
    }
    return 0;
}
// 打印結(jié)果
objc[70971]: ##############
objc[70971]: AUTORELEASE POOLS for thread 0x1000d5dc0
objc[70971]: 6 releases pending.
objc[70971]: [0x10080c000]  ................  PAGE  (hot) (cold)
objc[70971]: [0x10080c038]  ################  POOL 0x10080c038
objc[70971]: [0x10080c040]       0x100683ca0  NSObject
objc[70971]: [0x10080c048]  ################  POOL 0x10080c048
objc[70971]: [0x10080c050]       0x100683e40  NSObject
objc[70971]: [0x10080c058]  ################  POOL 0x10080c058
objc[70971]: [0x10080c060]       0x100683b90  NSObject
objc[70971]: ##############

在看另外一種打印

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *objc = [[NSObject alloc] autorelease];
        NSLog(@"--------objc = %@",objc);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{        // 子線程有一個(gè)隱性的autoreleasepool
            @autoreleasepool {
                sleep(1);
                NSObject *obj = [[NSObject alloc] autorelease];
                NSLog(@"+++++++obj = %@",obj);
                _objc_autoreleasePoolPrint();
            }
        });
        _objc_autoreleasePoolPrint();
    }
    sleep(2);          // 防止子線程沒有執(zhí)行完
    return 0;
}

// 打印結(jié)果
Test[74472:1192459] --------objc = <NSObject: 0x10290d0b0>
objc[74472]: ##############                                     // 第一個(gè)autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x1000d5dc0                  // 依賴的線程不同
objc[74472]: 2 releases pending.
objc[74472]: [0x10300a000]  ................  PAGE  (hot) (cold)
objc[74472]: [0x10300a038]  ################  POOL 0x10300a038
objc[74472]: [0x10300a040]       0x10290d0b0  NSObject
objc[74472]: ##############
KCObjcTest[74472:1192759] +++++++obj = <NSObject: 0x102804ac0>
objc[74472]: ##############                  // 第二個(gè)autoleasepool
objc[74472]: AUTORELEASE POOLS for thread 0x70000b669000
objc[74472]: 3 releases pending.
objc[74472]: [0x10380c000]  ................  PAGE  (hot) (cold)
objc[74472]: [0x10380c038]  ################  POOL 0x10380c038        // 一個(gè)隱性autoreleasepool
objc[74472]: [0x10380c040]  ################  POOL 0x10380c040        //手動(dòng)添加的autoreleasepool
objc[74472]: [0x10380c048]       0x102804ac0  NSObject
objc[74472]: ##############
  • 1湖笨、相同的線程旗扑,嵌套autoreleasePool 用的是相同的page,但是每個(gè)autoreleasepool 都會(huì)添加一個(gè)邊界對(duì)象(哨兵)
  • 2慈省、 不同的線程 使用不同的autoreleasePool

面試題

來源

  • 1臀防、ARC 下什么樣的對(duì)象由 Autoreleasepool 管理
    當(dāng)使用alloc/new/copy/mutableCopy開始的方法進(jìn)行初始化時(shí),會(huì)生成并持有對(duì)象(也就是不需要pool管理边败,系統(tǒng)會(huì)自動(dòng)的幫他在合適位置release)

    不是通過alloc等出來的對(duì)象 會(huì)加入自動(dòng)釋放池 比如id obj = [NSMutableArray array];

  • 2袱衷、Autoreleasepool 與 Runloop 的關(guān)系
    主線程默認(rèn)為我們開啟 Runloop,Runloop 會(huì)自動(dòng)幫我們創(chuàng)建Autoreleasepool笑窜,并進(jìn)行Push致燥、Pop 等操作來進(jìn)行內(nèi)存管理

  • 3、子線程默認(rèn)不會(huì)開啟 Runloop排截,那出現(xiàn) Autorelease 對(duì)象如何處理嫌蚤?不手動(dòng)處理會(huì)內(nèi)存泄漏嗎辐益?

    在子線程你創(chuàng)建了 Pool 的話,產(chǎn)生的 Autorelease 對(duì)象就會(huì)交給 pool 去管理脱吱。如果你沒有創(chuàng)建 Pool 智政,但是產(chǎn)生了 Autorelease 對(duì)象,就會(huì)調(diào)用 autoreleaseNoPage 方法箱蝠。在這個(gè)方法中续捂,會(huì)自動(dòng)幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage,如果你還是不理解抡锈,可以先看看 Autoreleasepool 的源代碼疾忍,再來看這個(gè)問題 ),并調(diào)用page->add(obj)將對(duì)象添加到 AutoreleasePoolPage 的棧中床三,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末一罩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撇簿,更是在濱河造成了極大的恐慌聂渊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件四瘫,死亡現(xiàn)場(chǎng)離奇詭異汉嗽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)找蜜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門饼暑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洗做,你說我怎么就攤上這事弓叛。” “怎么了诚纸?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵撰筷,是天一觀的道長。 經(jīng)常有香客問我畦徘,道長毕籽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任井辆,我火速辦了婚禮关筒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杯缺。我一直安慰自己平委,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布夺谁。 她就那樣靜靜地躺著廉赔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匾鸥。 梳的紋絲不亂的頭發(fā)上蜡塌,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音勿负,去河邊找鬼馏艾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奴愉,可吹牛的內(nèi)容都是我干的琅摩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼锭硼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼房资!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起檀头,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤轰异,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后暑始,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搭独,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年廊镜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牙肝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嗤朴,死狀恐怖配椭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情播赁,我是刑警寧澤颂郎,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站容为,受9級(jí)特大地震影響乓序,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坎背,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一替劈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧得滤,春花似錦陨献、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽急膀。三九已至,卻和暖如春龄捡,著一層夾襖步出監(jiān)牢的瞬間卓嫂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國打工聘殖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晨雳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓奸腺,卻偏偏與公主長得像餐禁,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子突照,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350