一、autoreleasePool 自動(dòng)釋放池補(bǔ)充
1. 在MRC下鬼店,為什么需要有自動(dòng)釋放池网棍?
- 自動(dòng)釋放池的作用:延遲釋放,使用方便
- 上述代碼中妇智,我們可以在大括號內(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];
- 從
AutoreleasePoolPage
的 new
方法中看到,每個(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)行釋放谷朝,而
viewDidLoad
和 viewWillAppear
恰好屬于一次 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為我們做了什么?參考資料棠绘。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
@autoreleasepool
實(shí)際上被轉(zhuǎn)換成了 __AtAutoreleasePool __autoreleasepool;
险领,這句代碼是作用是聲明了一個(gè)結(jié)構(gòu)體,那么 __AtAutoreleasePool
這個(gè)結(jié)構(gòu)體有什么內(nèi)容呢秒紧?objc_autoreleasePoolPush()
和 objc_autoreleasePoolPop(atautoreleasepoolobj)
這兩個(gè)函數(shù)干了什么?objc4源碼
中尋找他們的蹤影。NSObject.mm
我們發(fā)現(xiàn)了這兩個(gè)函數(shù)的蹤影AutoreleasePoolPage
叙淌,我需要理解它AutoreleasePoolPage
的理解AutoreleasePoolPage * const parent;
和 AutoreleasePoolPage *child;
這兩個(gè)成員變量秤掌,我們可以大概得出,這個(gè)是一個(gè)雙向鏈表
鹰霍。AutoreleasePoolPage
的圖解autorelease 對象
: 是在 MRC 模式下闻鉴,如下創(chuàng)建的對象:Person *person = [[[Person alloc] init] autorelease];
AutoreleasePoolPage
的 new
方法中看到,每個(gè)AutoreleasePoolPage
對象占用 4096
個(gè)字節(jié)內(nèi)存茂洒,除了用來存放它內(nèi)部的成員變量孟岛,剩下的空間來存放 autorelease 對象
的地址AutoreleasePoolPage
對象通過雙向鏈表
的形式連接在一起AutoreleasePoolPage
大概能存儲 500個(gè)對象指針,當(dāng) AutoreleasePoolPage
放滿之后督勺,會創(chuàng)建新的AutoreleasePoolPage
渠羞,并將 child 指針指向新的AutoreleasePoolPage
;將新的AutoreleasePoolPage
parent 指針指向源 AutoreleasePoolPage
玷氏;這樣就形成了雙向鏈表AutoreleasePoolPage
的存儲情況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]: ##############
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
YYPerson *person = [[[YYPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}
@autoreleasepool
大括號結(jié)束時(shí)馬上釋放世落,這就是我們前面學(xué)的知識- (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__);
}
person
會在 viewWillAppear
結(jié)束后才被釋放viewDidLoad
和 viewWillAppear
恰好屬于一次 RunLoop 的循環(huán)內(nèi)。for (int i = 0; i < 5000000; i++) {
NSObject *obj = [[NSObject alloc] init];
}
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();
}
}
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
- (void)viewDidLoad {
[super viewDidLoad];
YYPerson *person = [[YYPerson alloc] init] ;
_objc_autoreleasePoolPrint();
NSLog(@"%s", __func__);
}
_objc_autoreleasePoolPrint();
打印日志來看 person
并沒有加入到自動(dòng)釋放池中NSLog(@"%s", __func__);
之后被釋放了[person release]
這樣的代碼向族,所以才會有這種結(jié)果