Autoreleasepool

局部釋放池

創(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ī)則:

  1. 自己生成的對象自己持有
  2. 非自己生成的對象秃励,自己也能持有
  3. 不再需要自己持有的對象時必須釋放
  4. 自己不持有的對象無法釋放

那要不這樣寫吧:

@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)存占用。偷一張土土哥博客的圖來說明:

image.png

黑幕背后的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)存的分配與釋放

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市椅亚,隨后出現(xiàn)的幾起案子限番,更是在濱河造成了極大的恐慌呀舔,老刑警劉巖媚赖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惧磺,死亡現(xiàn)場離奇詭異,居然都是意外死亡缤底,警方通過查閱死者的電腦和手機(jī)个唧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門徙歼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魄梯,“玉大人酿秸,你說我怎么就攤上這事≡噬龋” “怎么了考润?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唱矛。 經(jīng)常有香客問我井辜,道長,這世上最難降的妖魔是什么窃肠? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任冤留,我火速辦了婚禮纤怒,結(jié)果婚禮上天通,老公的妹妹穿的比我還像新娘像寒。我一直安慰自己诺祸,他們只是感情好序臂,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布奥秆。 她就那樣靜靜地躺著构订,像睡著了一般侮叮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悼瘾,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音亥宿,去河邊找鬼。 笑死烫扼,一個胖子當(dāng)著我的面吹牛曙求,可吹牛的內(nèi)容都是我干的映企。 我是一名探鬼主播悟狱,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼堰氓!你這毒婦竟也來了挤渐?” 一聲冷哼從身側(cè)響起双絮,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掷邦,失蹤者是張志新(化名)和其女友劉穎抚岗,沒想到半個月后宣蔚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體向抢,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年亩冬,在試婚紗的時候發(fā)現(xiàn)自己被綠了艘希。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硼身。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖覆享,靈堂內(nèi)的尸體忽然破棺而出佳遂,到底是詐尸還是另有隱情,我是刑警寧澤撒顿,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布丑罪,位于F島的核電站,受9級特大地震影響凤壁,放射性物質(zhì)發(fā)生泄漏吩屹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一拧抖、第九天 我趴在偏房一處隱蔽的房頂上張望煤搜。 院中可真熱鬧,春花似錦徙鱼、人聲如沸宅楞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厌衙。三九已至,卻和暖如春绞绒,著一層夾襖步出監(jiān)牢的瞬間婶希,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工蓬衡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留喻杈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓狰晚,卻偏偏與公主長得像筒饰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壁晒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

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

  • 1瓷们、[NSObject alloc]在創(chuàng)建完對象后,會讓該對象的retainCount+1秒咐,后續(xù)的init為初始化...
    naiyi閱讀 1,534評論 0 4
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,416評論 8 265
  • 1.趣味思考 ARC機(jī)制下對象指針有三種修飾符谬晕,分別是__ strong、__ autoreleasing携取、__ ...
    Emiya_zhang閱讀 2,261評論 8 3
  • OC語言基礎(chǔ) 1.類與對象 類方法 OC的類方法只有2種:靜態(tài)方法和實例方法兩種 在OC中攒钳,只要方法聲明在@int...
    奇異果好補閱讀 4,282評論 0 11
  • 所有的風(fēng) 只有一個方向 所有的燈 只為一部影像 我 在風(fēng)中沉睡 在燈下迷醉 醒來時 秋風(fēng)無處涼, 落葉夢一場雷滋。 原...
    北覺閱讀 200評論 0 0