iOS 底層 day26 內(nèi)存管理 自動(dòng)釋放池 @autoreleasepool 醉蚁、臨時(shí)變量釋放

一、autoreleasePool 自動(dòng)釋放池補(bǔ)充

1. 在MRC下鬼店,為什么需要有自動(dòng)釋放池网棍?
  • 自動(dòng)釋放池的作用:延遲釋放,使用方便
image.png
  • 上述代碼中妇智,我們可以在大括號內(nèi)隨意使用person變量滥玷,不用擔(dān)心過早釋放,或者忘記釋放的巍棱,造成內(nèi)存泄漏的問題惑畴。
  • 讓編碼更加簡潔

2、在MRC下航徙,實(shí)例對象調(diào)用 -[NSObject autorelease] 方法加入自動(dòng)釋放池如贷,對象的引用計(jì)數(shù)會有變化嗎?

  • 在加入自動(dòng)釋放池時(shí)到踏,引用計(jì)數(shù)器不會發(fā)生改變
  • 在自動(dòng)釋放池進(jìn)行對象釋放時(shí)杠袱,引用計(jì)數(shù)器會 -1

3、簡述 Runloop 和 自動(dòng)釋放池有什么關(guān)系窝稿?

  • ① Runloop 進(jìn)入的時(shí)候會創(chuàng)建一個(gè)自動(dòng)釋放池

  • ② Runloop 即將進(jìn)入休眠的時(shí)候楣富,會銷毀自動(dòng)釋放池,并且新創(chuàng)建一個(gè)自動(dòng)釋放池

  • ③ Runloop 在即將退出的時(shí)候伴榔,會銷毀自動(dòng)釋放池

  • 上面三個(gè)步驟纹蝴,形成了一個(gè)閉環(huán)。

  • 【補(bǔ)充】Runloop在創(chuàng)建的時(shí)候踪少,默認(rèn)會創(chuàng)建兩個(gè)Observers塘安,用于監(jiān)聽Runloop的 進(jìn)入即將進(jìn)入休眠援奢、即將退出 三個(gè)階段兼犯,然后進(jìn)行自動(dòng)釋放池的相關(guān)操作。

二、autoreleasePool 自動(dòng)釋放池

1. 我們經(jīng)趁舛迹可以在 main 函數(shù)中看到 @autoreleasepool,這明顯是一個(gè)編譯器的語法糖帆竹,思考碰到這種語法糖绕娘,我們?nèi)绾巫屗萎吢叮?/h5>
  • 我們可以通過指令將其轉(zhuǎn)換成 C++代碼,這樣我們就能看到其本質(zhì)代碼了栽连。
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
轉(zhuǎn)換后的 main 代碼
  • 我們可以看到 @autoreleasepool實(shí)際上被轉(zhuǎn)換成了 __AtAutoreleasePool __autoreleasepool; 险领,這句代碼是作用是聲明了一個(gè)結(jié)構(gòu)體,那么 __AtAutoreleasePool 這個(gè)結(jié)構(gòu)體有什么內(nèi)容呢秒紧?
__AtAutoreleasePool 結(jié)構(gòu)體
  • 所以根據(jù)這個(gè)結(jié)構(gòu)體的特性(需要一點(diǎn)點(diǎn) C++知識)绢陌,上述代碼可以被我們這樣理解:
main 函數(shù)的實(shí)際代碼效果
2. objc_autoreleasePoolPush()objc_autoreleasePoolPop(atautoreleasepoolobj) 這兩個(gè)函數(shù)干了什么?
  • 這兩個(gè)函數(shù)的從我們編譯后的 cpp 文件中只有聲明熔恢,找不到實(shí)現(xiàn)脐湾,我們嘗試從 objc4源碼 中尋找他們的蹤影。
  • NSObject.mm 我們發(fā)現(xiàn)了這兩個(gè)函數(shù)的蹤影
  • 我們發(fā)現(xiàn)這兩個(gè)函數(shù)內(nèi)部涉及到一個(gè)很頻繁的對象 AutoreleasePoolPage 叙淌,我需要理解它
3. AutoreleasePoolPage 的理解
AutoreleasePoolPage結(jié)構(gòu)體的內(nèi)部成員變量
  • AutoreleasePoolPage * const parent;AutoreleasePoolPage *child; 這兩個(gè)成員變量秤掌,我們可以大概得出,這個(gè)是一個(gè)雙向鏈表鹰霍。
4. AutoreleasePoolPage 的圖解
  • autorelease 對象: 是在 MRC 模式下闻鉴,如下創(chuàng)建的對象:Person *person = [[[Person alloc] init] autorelease];
  • AutoreleasePoolPagenew方法中看到,每個(gè)AutoreleasePoolPage對象占用 4096 個(gè)字節(jié)內(nèi)存茂洒,除了用來存放它內(nèi)部的成員變量孟岛,剩下的空間來存放 autorelease 對象的地址
  • 所有的AutoreleasePoolPage對象通過雙向鏈表的形式連接在一起
  • 一個(gè)AutoreleasePoolPage大概能存儲 500個(gè)對象指針,當(dāng) AutoreleasePoolPage放滿之后督勺,會創(chuàng)建新的AutoreleasePoolPage渠羞,并將 child 指針指向新的AutoreleasePoolPage;將新的AutoreleasePoolPage parent 指針指向源 AutoreleasePoolPage玷氏;這樣就形成了雙向鏈表
AutoreleasePoolPage雙向列表圖解
5. 思考 MRC 環(huán)境下堵未,下列代碼在 AutoreleasePoolPage的存儲情況
示例代碼
  • 存儲結(jié)構(gòu)情況
存儲結(jié)構(gòu)情況
  • POOL_BOUNDARY 是標(biāo)記位,本質(zhì)上的值就是 0盏触,它用于標(biāo)記每個(gè)自動(dòng)釋放池的開始位置

  • 每增加一個(gè) POOL_BOUNDARY 渗蟹,表示代碼中創(chuàng)建了一個(gè) @autoreleasepool

  • AutoreleasePoolPage 是一個(gè)棧結(jié)構(gòu),具有后進(jìn)先出的特點(diǎn)

  • 我們還可以通過聲明一個(gè) Foundation 內(nèi)部函數(shù) :extern void _objc_autoreleasePoolPrint(void);來從 Xcode 日志中查看自動(dòng)釋放池情況赞辩。

  • 我們在 Person *p4 = [[[Person alloc] init] autorelease]; 后面加上代碼_objc_autoreleasePoolPrint();

  • 獲得如下打印雌芽,印證我們上圖的想法是正確的

objc[7180]: ##############
objc[7180]: AUTORELEASE POOLS for thread 0x1000d3dc0
objc[7180]: 7 releases pending.
objc[7180]: [0x10280e000]  ................  PAGE  (hot) (cold)
objc[7180]: [0x10280e038]  ################  POOL 0x10280e038
objc[7180]: [0x10280e040]       0x100637410  Person
objc[7180]: [0x10280e048]       0x100637470  Person
objc[7180]: [0x10280e050]  ################  POOL 0x10280e050
objc[7180]: [0x10280e058]       0x100637970  Person
objc[7180]: [0x10280e060]  ################  POOL 0x10280e060
objc[7180]: [0x10280e068]       0x100637990  Person
objc[7180]: ##############
5. 請問MRC 環(huán)境下,下面的person 什么時(shí)候釋放辨嗽?
- (void)viewDidLoad {
    [super viewDidLoad];
    @autoreleasepool {
        YYPerson *person = [[[YYPerson alloc] init] autorelease];
    }
    NSLog(@"%s", __func__);
}
  • @autoreleasepool 大括號結(jié)束時(shí)馬上釋放世落,這就是我們前面學(xué)的知識
6. 請問MRC 環(huán)境下,下面的person 什么時(shí)候釋放糟需?
- (void)viewDidLoad {
    [super viewDidLoad];
    YYPerson *person = [[[YYPerson alloc] init] autorelease];
    NSLog(@"%s", __func__);
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"%s", __func__);
}
  • 我們從打印結(jié)果來看 person會在 viewWillAppear 結(jié)束后才被釋放
  • 這是因?yàn)?RunLoop 進(jìn)入休眠前屉佳,會將自動(dòng)釋放池里面的對象進(jìn)行釋放谷朝,而 viewDidLoadviewWillAppear 恰好屬于一次 RunLoop 的循環(huán)內(nèi)。

三武花、ARC為我們做了什么圆凰?

1. 正常情況下,ARC是如何幫助我們自動(dòng)管理內(nèi)容的体箕?
  • 項(xiàng)目設(shè)置了ARC后专钉,編譯器在編譯期自動(dòng)插入內(nèi)存管理的代碼。
2. 既然有了 ARC累铅,為什么還需要 AutoreleasePool呢跃须?(個(gè)人見解,胡說八道的)
  • 【見解】其實(shí)ARC也可以舍棄Autorelease這個(gè)概念娃兽,并且規(guī)定所有從方法中返回的對象保留引用計(jì)數(shù)比期望值多1菇民,但是這么做破壞了向后的兼容性,需要考慮到不使用ARC的代碼投储。
  • 【見解】所以筆者的理解玉雾,Autorelease是對ARC內(nèi)存管理一種兼容性優(yōu)化方案。并且由于ARC下我們無法對對象的引用計(jì)數(shù)進(jìn)行操作轻要,Autorelease靈活補(bǔ)充了ARC對對象生命周期的控制复旬,使開發(fā)者可以在一個(gè)Runloop的范圍內(nèi)對對象進(jìn)行延遲或提前釋放控制。
3. 我們是什么情況下冲泥,手動(dòng)創(chuàng)建自動(dòng)釋放池驹碍?(官方說明
  • 如果您正在編寫不基于 UI 框架的程序,例如命令行工具凡恍。
  • 如果您編寫一個(gè)創(chuàng)建許多臨時(shí)對象的循環(huán)志秃。您可以在循環(huán)內(nèi)使用自動(dòng)釋放池塊在下一次迭代之前處理這些對象。在循環(huán)中使用自動(dòng)釋放池塊有助于減少應(yīng)用程序的最大內(nèi)存占用嚼酝。
  • 如果你產(chǎn)生一個(gè)輔助線程浮还。您必須在線程開始執(zhí)行后立即創(chuàng)建自己的自動(dòng)釋放池塊;否則闽巩,您的應(yīng)用程序?qū)⑿孤ο蟆?/li>
4. 手動(dòng)創(chuàng)建自動(dòng)釋放池之臨時(shí)變量釋放钧舌,詳解?
  • 【問題】下面代碼??涎跨,會導(dǎo)致內(nèi)存暴增嗎洼冻?
for (int i = 0; i < 5000000; i++) {
     NSObject *obj = [[NSObject alloc] init];  
}
  • 【結(jié)論】不會,obj 在每一次for循環(huán)結(jié)束隅很,就會得到釋放
  • 【問題】下面代碼??撞牢,會導(dǎo)致內(nèi)存暴增嗎?
    for (int i = 0; i < 5000000; i++) {
        NSString *fileContents = [NSString stringWithFormat:@"NSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncodingNSUTF8StringEncoding%i",i];
            NSLog(@"%i, %p", i, p);
    }
  • 【結(jié)論】會,fileContents 在每一次for循環(huán)結(jié)束屋彪,不會釋放

  • 【問題】下面代碼??所宰,會導(dǎo)致內(nèi)存暴增嗎?【蘋果官方例子】

    for (int i = 0; i < 5000000; i++) {
            NSError *error;
            NSString *fileContents = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"https://news-at.zhihu.com/api/4/news/latest"]
                                             encoding:NSUTF8StringEncoding error:&error];
            NSLog(@"%i, %p", i, fileContents);
    }
  • 【結(jié)論】會畜挥,fileContents 在每一次for循環(huán)結(jié)束歧匈,不會釋放

  • 【問題】我們發(fā)現(xiàn)在for循環(huán)中,并非所有臨時(shí)對象都不會被釋放砰嘁,那么什么情況下會釋放什么時(shí)候不會釋放呢?

  • 【看下面代碼分析】

extern void _objc_autoreleasePoolPrint(void);

    @autoreleasepool {
        @autoreleasepool {
            NSObject *obj = [NSObject alloc];
            NSError *error;
            NSString *fileContents = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"https://news-at.zhihu.com/api/4/news/latest"]
                                             encoding:NSUTF8StringEncoding error:&error];
            _objc_autoreleasePoolPrint();
        }
    }
  • 上述代碼輸出結(jié)果
objc[7293]: [0x1010123a0]  ################  POOL 0x1010123a0
objc[7293]: [0x1010123a8]  ################  POOL 0x1010123a8
objc[7293]: [0x1010123b0]       0x280e933f0  __NSCFString
objc[7293]: [0x1010123b8]       0x280e92c40  __NSCFString
objc[7293]: [0x1010123c0]       0x280e93120  __NSCFString
objc[7293]: [0x1010123c8]       0x280e93330  __NSCFString
objc[7293]: [0x1010123d0]       0x2800ff800  NSHTTPURLResponse
objc[7293]: [0x1010123d8]       0x280e93000  __NSCFData
  • 【結(jié)論】在ARC勘究,只有放入自動(dòng)釋放池的臨時(shí)對象矮湘,才需要進(jìn)行釋放
5.蘋果的ARC和Java的垃圾回收機(jī)制對比?
  • 蘋果的官方說明中稱口糕,ARC是“由編譯器進(jìn)行內(nèi)存管理”的技術(shù)缅阳,其本質(zhì)和Java的垃圾回收機(jī)制有區(qū)別。
  • 并且編譯器并不能完全勝任內(nèi)存管理景描。
6. 在 ARC 的環(huán)境下十办,下面 person 在什么時(shí)候釋放?
- (void)viewDidLoad {
    [super viewDidLoad];
    YYPerson *person = [[YYPerson alloc] init] ;
    _objc_autoreleasePoolPrint();
    NSLog(@"%s", __func__);
}
  • 我們從_objc_autoreleasePoolPrint();打印日志來看 person并沒有加入到自動(dòng)釋放池中
  • person 在 NSLog(@"%s", __func__); 之后被釋放了
  • 其實(shí)在 ARC 環(huán)境下超棺,上述代碼編譯器會給我們在大括號結(jié)束處加上[person release] 這樣的代碼向族,所以才會有這種結(jié)果
7. ARC為我們做了什么?參考資料棠绘。

http://luoxianming.cn/2017/05/06/arc/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末件相,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子氧苍,更是在濱河造成了極大的恐慌夜矗,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件让虐,死亡現(xiàn)場離奇詭異紊撕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赡突,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門对扶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惭缰,你說我怎么就攤上這事辩稽。” “怎么了从媚?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵逞泄,是天一觀的道長。 經(jīng)常有香客問我,道長喷众,這世上最難降的妖魔是什么各谚? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮到千,結(jié)果婚禮上昌渤,老公的妹妹穿的比我還像新娘。我一直安慰自己憔四,他們只是感情好膀息,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著了赵,像睡著了一般潜支。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柿汛,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天冗酿,我揣著相機(jī)與錄音,去河邊找鬼络断。 笑死裁替,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的貌笨。 我是一名探鬼主播弱判,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锥惋!你這毒婦竟也來了裕循?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤净刮,失蹤者是張志新(化名)和其女友劉穎剥哑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淹父,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡土陪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年珊随,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茴丰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片召衔。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蘸际,靈堂內(nèi)的尸體忽然破棺而出座哩,到底是詐尸還是另有隱情,我是刑警寧澤粮彤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布根穷,位于F島的核電站姜骡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屿良。R本人自食惡果不足惜圈澈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尘惧。 院中可真熱鬧康栈,春花似錦、人聲如沸喷橙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贰逾。三九已至悬荣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間似踱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工稽煤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留核芽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓酵熙,卻偏偏與公主長得像轧简,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子匾二,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359