局部釋放池
創(chuàng)建一個新的自動釋放池的方法:
ARC下:
@autoreleasepool {
Student *s = [[Student alloc] init];
}
這相當(dāng)于MRC下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init;
Student *s = [[Student alloc] init];
[pool drain];
其中對象s會被加入到自動釋放池爵憎,當(dāng)ARC下代碼執(zhí)行到右大括號時(相當(dāng)于MRC執(zhí)行代碼[pool drain];
)會對池中所有對象依次執(zhí)行一次release
操作
等等,在ARC下我直接這樣寫:
{
Student *s = [[Student alloc] init];
}
MRC下我直接這樣寫:
Student *s = [[Student alloc] init];
[s release];
效果和你用自動釋放池是一樣的啊婚瓜,那我為什么要用這破玩意啊宝鼓,還要初始化對象浪費時間浪費內(nèi)存沥寥。
以上用到的
Autoreleasepool
叫做局部釋放池,在特定場景下有其用處,后面會詳細(xì)講解。
Autoreleasepool使用場景
還記得MRC下這么一種情況嗎:
@implementation Student
+ (instancetype)student {
Student *student = [[Student alloc] init];
return student;
}
@end
分析一下:student創(chuàng)建并持有對象队贱,返回值對象引用計數(shù)為1
然后調(diào)用的地方:
Student *s = [Student student];
s一通快樂的使用慎式,唯獨最后忘記調(diào)用[s release];
掌眠,然后對象引用計數(shù)始終是1內(nèi)存泄漏渺尘,因為調(diào)用者認(rèn)為我都沒有對對象做retain操作枫匾,我不持有對象等脂,你為什么讓我釋放對象争涌,這不符合內(nèi)存管理規(guī)則,崩潰了你負(fù)責(zé)盎乩?耕皮!不明白?請默念內(nèi)存管理規(guī)則:
- 自己生成的對象自己持有
- 非自己生成的對象秃励,自己也能持有
- 不再需要自己持有的對象時必須釋放
- 自己不持有的對象無法釋放
那要不這樣寫吧:
@implementation Student
+ (instancetype)student {
Student *student = [[Student alloc] init];
[student release];
return student;
}
@end
分析一下:student創(chuàng)建并持有對象慷蠕,對象引用計數(shù)為1干旧,student釋放對象,對象引用計數(shù)為0赏半,對象釋放剑勾,返回值對象為...哪他媽還有返回值對象啊對象被釋放了
既然不能自己釋放對象募寨,也不能要求調(diào)用方用完對象之后釋放格郁,那該怎么辦呢?這時候就可以看到使用Autoreleasepool
的正確姿勢了:
@implementation Student
+ (instancetype)student {
Student *student = [[Student alloc] init];
[student autorelease];
return student;
}
@end
調(diào)用方:
Student *s = [Student student];
[s retain];
// work
[s release];
通過調(diào)用autorelease
方法把釋放對象的任務(wù)交給Autoreleasepool
對象寺旺,當(dāng)Autoreleasepool
對象執(zhí)行到[pool drain]
方法的時候會對自動釋放池中所有的對象執(zhí)行一次release
操作极颓,然后+ (instancetype)student
方法中創(chuàng)建的對象引用計數(shù)就會被-1了,如果引用計數(shù)變?yōu)?了,對象自然就釋放了
調(diào)用方獲取對象以后自己retain
持有一下對象吴汪,防止使用期間對象被釋放了觉渴,用完release
釋放一下
以上只是在MRC下自動釋放的一種使用情形誉碴,在ARC時這種情形由編譯器自動為我們加上
__autorealeasing
修飾符id __autorealeasing obj = [NSMutableArray array];
奋岁,作用和MRC下調(diào)用autorelease
方法一樣够话,ARC 還有什么對象由 Autoreleasepool 管理呢媒至,可以參看:引用計數(shù)帶來的一次討論
RunLoop釋放池
看似很完美了,但是有沒有想過我們直接調(diào)用autorelease
方法就可以把釋放對象的任務(wù)交給Autoreleasepool
對象大溜,Autoreleasepool
對象從哪里來钦奋?Autoreleasepool
對象又會在何時調(diào)用[pool drain]
方法座云?
要解答以上兩個問題,首先要了解NSRunLoop
付材,可以看下ibireme 的 深入理解RunLoop,原話如下:
App啟動后疙教,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()伞租。
第一個 Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)贞谓,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是-2147483647葵诈,優(yōu)先級最高裸弦,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池作喘;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池理疙。這個 Observer 的 order 是 2147483647,優(yōu)先級最低泞坦,保證其釋放池子發(fā)生在其他所有回調(diào)之后窖贤。
對于每一個Runloop運行循環(huán),系統(tǒng)會隱式創(chuàng)建一個Autoreleasepool
對象贰锁,+ (instancetype)student
中執(zhí)行autorelease
的操作就會將student
對象添加到這個系統(tǒng)隱式創(chuàng)建的
Autoreleasepool
對象中——這回答了Autoreleasepool對象從哪里來
當(dāng)Runloop執(zhí)行完一系列動作沒有更多事情要它做時赃梧,它會進(jìn)入休眠狀態(tài),避免一直占用大量系統(tǒng)資源豌熄,或者Runloop要退出時會觸發(fā)執(zhí)行_objc_autoreleasePoolPop()
方法相當(dāng)于讓Autoreleasepool
對象執(zhí)行一次drain
方法授嘀,Autoreleasepool
對象會對自動釋放池中所有的對象依次執(zhí)行依次release
操作——這回答了Autoreleasepool對象又會在何時調(diào)用[pool drain]方法
子線程上的Autorelease
子線程默認(rèn)不會開啟 Runloop,那出現(xiàn) Autorelease 對象如何處理锣险?不手動處理會內(nèi)存泄漏嗎蹄皱?
感覺比較難以回答,需要很細(xì)致的讀過 Runtime 芯肤、Autoreleasepool 的源碼才可以巷折。我也是參考了 StackOverFlow 的回答:does NSThread create autoreleasepool automaticly now?。我再來簡單闡述下崖咨,在子線程你創(chuàng)建了 Pool 的話锻拘,產(chǎn)生的 Autorelease 對象就會交給 pool 去管理。如果你沒有創(chuàng)建 Pool 掩幢,但是產(chǎn)生了 Autorelease 對象逊拍,就會調(diào)用 autoreleaseNoPage 方法上鞠。在這個方法中,會自動幫你創(chuàng)建一個 hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage芯丧,如果你還是不理解芍阎,可以先看看 Autoreleasepool 的源代碼,再來看這個問題 )缨恒,并調(diào)用 page->add(obj)
將對象添加到 AutoreleasePoolPage 的棧中谴咸,也就是說你不進(jìn)行手動的內(nèi)存管理,也不會內(nèi)存泄漏啦骗露!StackOverFlow 的作者也說道岭佳,這個是 OS X 10.9+和 iOS 7+ 才加入的特性。并且蘋果沒有對應(yīng)的官方文檔闡述此事萧锉,但是你可以通過源碼了解珊随。
嗯,這里是照抄的柿隙,只是為了給自己做個筆記
作者:Joy___
鏈接:http://www.reibang.com/p/f87f40592023
來源:簡書
著作權(quán)歸作者所有叶洞。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處禀崖。
看個例子
要點一: 當(dāng)AutoreleasePool出現(xiàn)嵌套的時候衩辟,在內(nèi)層調(diào)用autorelease
方法,會把對象添加到內(nèi)層的AutoreleasePool對象生成的自動釋放池里波附。具體實現(xiàn)可以看黑幕背后的Autorelease
要點二: 由于這個vc在loadView之后便add到了window層級上艺晴,所以viewDidLoad和viewWillAppear是在同一個runloop調(diào)用的。同樣了解自黑幕背后的Autorelease
要點三:將對象放入自動釋放池不會引起引用計數(shù)+1
在查資料是看到AutoreleasePool詳解和runloop的關(guān)系中有如下實驗代碼:
@interface ViewController ()
@property (nonatomic, weak) NSString *string_weak;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 場景 1
NSString *string = [NSString stringWithFormat:@"1234567890"];
self.string_weak = string;
NSLog(@"string: %@",self.string_weak);
//場景 2
// @autoreleasepool {
// NSString *string = [NSString stringWithFormat:@"1234567890"];
// _string_weak = string;
// }
// NSLog(@"string: %@",_string_weak);
// 場景 3
// NSString *string = nil;
// @autoreleasepool {
// string = [NSString stringWithFormat:@"1234567890"];
// _string_weak = string;
// }
// NSLog(@"string: %@",self.string_weak);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"string: %@", self.string_weak);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"string: %@", self.string_weak);
}
@end
執(zhí)行后打印結(jié)果為
// 場景 1
2018-05-27 00:58:17.791343+0800 原子操作[14045:1419769] string: 1234567890
2018-05-27 00:58:17.791570+0800 原子操作[14045:1419769] string: 1234567890
2018-05-27 00:58:17.794914+0800 原子操作[14045:1419769] string: (null)
// 場景 2
2018-05-27 00:59:00.764576+0800 原子操作[14063:1420728] string: (null)
2018-05-27 00:59:00.764798+0800 原子操作[14063:1420728] string: (null)
2018-05-27 00:59:00.767894+0800 原子操作[14063:1420728] string: (null)
// 場景 3
2018-05-27 00:59:30.974246+0800 原子操作[14079:1421468] string: 1234567890
2018-05-27 00:59:30.974407+0800 原子操作[14079:1421468] string: (null)
2018-05-27 00:59:30.977082+0800 原子操作[14079:1421468] string: (null)
原博是按照對象加入自動釋放池會讓對象引用計數(shù)+1解釋掸屡,并且解釋通了封寞,但是請注意以下代碼與打印(MRC下):
+ (instancetype)student {
Student *student = [[Student alloc] init];
NSLog(@"創(chuàng)建后引用計數(shù):%lu",(unsigned long)[student retainCount]);
[student autorelease];
NSLog(@"加入自動釋放池后引用計數(shù):%lu",(unsigned long)[student retainCount]);
return student;
}
- (void)viewDidLoad {
[super viewDidLoad];
Student *s = [Student student];
NSLog(@"調(diào)用方獲取到對象時引用計數(shù):%lu",(unsigned long)[s retainCount]);
}
打印結(jié)果:
2018-05-27 01:05:32.839710+0800 MRC[14144:1427133] 創(chuàng)建后引用計數(shù):1
2018-05-27 01:05:32.839825+0800 MRC[14144:1427133] 加入自動釋放池后引用計數(shù):1
2018-05-27 01:05:32.839929+0800 MRC[14144:1427133] 調(diào)用方獲取到對象時引用計數(shù):1
對象引用計數(shù)始終為1折晦,加入自動釋放池不會讓引用計數(shù)+1钥星,我開始是按照博客所說對象加入自動釋放池會讓對象引用計數(shù)+1的說法理解的,但是當(dāng)看到這段代碼的時候產(chǎn)生了疑惑:
@implementation Student
+ (instancetype)student {
Student *student = [[Student alloc] init];
[student autorelease];
return student;
}
@end
將對象放入自動釋放池是為了等下自動釋放池能讓對象執(zhí)行一次release
操作自動釋放對象满着,讓對象的引用計數(shù)在這個方法體內(nèi)達(dá)到收支平衡,避免內(nèi)存泄漏贯莺。Student *student = [[Student alloc] init];
后對象引用計數(shù)為1风喇,[student autorelease];
后對象引用計數(shù)為2,當(dāng)Runloop即將進(jìn)入休眠時缕探,釋放自動釋放池魂莫,student對象會被執(zhí)行一次release
操作,引用計數(shù)變?yōu)?爹耗,還是釋放不了耙考!所以將對象放入自動釋放池不會引起引用計數(shù)+1
結(jié)合要點一二三谜喊,自己嘗試解釋場景1,2倦始,3吧
不過以上實驗代碼說明了我們可以手動干預(yù)Autorelease對象的釋放時機(jī)斗遏,利用這個我們就可以利用局部釋放池做些事情了
局部釋放池的應(yīng)用
看下邊這段代碼:
for (int i = 0; i < largeNumber; i++) {
NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}
要點:
-
[NSString stringWithFormat:@"hello -%04d", i]
方法創(chuàng)建的對象會加入到自動釋放池里,對象的釋放權(quán)交給了RunLoop 的釋放池 - RunLoop 的釋放池會等待Runloop即將進(jìn)入睡眠或者即將退出的時候釋放一次
- for循環(huán)中線程一直在做事情鞋邑,Runloop不會進(jìn)入睡眠
綜上:
上邊的代碼for循環(huán)生成的NSString對象會無法及時釋放诵次,造成瞬時內(nèi)存占用過大
解決辦法,每次循環(huán)時都手動創(chuàng)建一個局部釋放池枚碗,及時創(chuàng)建逾一,及時釋放,這樣NSString對象就會及時得到釋放
for (int i = 0; i < largeNumber; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"hello -%04d", i];
str = [str stringByAppendingString:@" - world"];
}
}
在for循環(huán)大量使用imageNamed:
之類的方法生成UIImage對象可能是個更要命的事情肮雨,內(nèi)存隨時可能因為占用過多被系統(tǒng)殺掉遵堵。
這種情況下利用Autoreleasepool可以大幅度降低程序的內(nèi)存占用。偷一張土土哥博客的圖來說明:
黑幕背后的Autorelease
我這里只是了解了Autorelease做的事情怨规,具體到代碼級別是怎么做的陌宿,以及Autorelease是怎樣的數(shù)據(jù)結(jié)構(gòu)可以看這里:黑幕背后的Autorelease
iOS 內(nèi)存管理 —— MRC & ARC
各個線程 Autorelease 對象的內(nèi)存管理
NSRunloop,runloop,autoReleasePool和thread的關(guān)系理解
RunLoop和autorelease的一道面試題
@autoreleasepool-內(nèi)存的分配與釋放