iOS內(nèi)存管理 - Autorelease 詳解

前言

比較晚入坑iOS的同學(xué)大多沒怎么使用過MRC(Manual Reference Counting),直接享受了ARC的便利点待,ARC(Automatic Reference Counting)從Xcode4引入,由于ARC下禁止直接調(diào)用retain璧尸、release遮咖、autorelease等函數(shù),甚至不需要了解autorelease這些函數(shù)就能很好的進行iOS開發(fā)懊烤。然而,為了知其所以然宽堆,還是有必要了解一下底層的原理腌紧。

一、 概述

首先明確一點畜隶,ARC和Autorelease沒有直接的聯(lián)系壁肋,在MRC時代就存在Autorelease:
ARC:代替手動添加內(nèi)存管理函數(shù)進行人工內(nèi)存管理(retain、release籽慢、autorelease)浸遗,編譯階段自動的在需要持有對象時插入retain函數(shù)、需要釋放時插入release函數(shù)(就是這么智能)箱亿;
Autorelease:向一個對象發(fā)送延遲釋放信息跛锌,使得這個對象可以在作用域意外范圍被使用;典型的例子就是將一個對象作為返回值給調(diào)用者届惋,如果不延遲釋放髓帽,這個返回值在出了所在函數(shù)范圍就被立即釋放,調(diào)用者拿到的永遠是nil脑豹,因為iOS的內(nèi)存遵循誰申請誰釋放的原則郑藏,當(dāng)向一個對象發(fā)送了autorelease消息,實際上就是將該對象放入autoreleasePool池子動晨缴,等到延遲到適當(dāng)?shù)臅r機(通常是在NSRunloop即將進入休眠或者退出時)進行釋放(對池子中的每個對象發(fā)送release消息)译秦。

二、 底層實現(xiàn)

大家都知道獲取一個NSMutableArray實例對象有好多種方式:如[NSMutableArray new]或者[NSMutableArray array]。
a. iOS很特別的一點就是用函數(shù)名字了表明內(nèi)存管理的方式(包含alloc筑悴、retain们拙、new、copy阁吝、mutablecopy等關(guān)鍵字的函數(shù)需要申請者負責(zé)釋放該實例對象)砚婆,而后者不包含上述關(guān)鍵字的函數(shù)不需要調(diào)用者負責(zé)該對象的釋放,所以其實上面方法后者不要調(diào)用者釋放該實例突勇,因為+ (NSMutableArray *)array函數(shù)內(nèi)部調(diào)用了autorelase函數(shù)装盯,如下為其底層實現(xiàn):

// + (NSMutableArray *)array 底層函數(shù)實現(xiàn)
+ (NSMutableArray *)array {
         NSMutableArray *arr = [[NSMutableArray alloc] init];
         return [arr autorelease];
}

在出array函數(shù)范圍之前先不釋放arr對象,將其放入autoreleasePool甲馋,待到下一次清空自動釋放池時對其發(fā)送一次release埂奈,剩下的就看調(diào)用者是否持有了該對象, 如果函數(shù)調(diào)用者持有了該arr對象定躏,則arr對象繼續(xù)存在账磺,否則如果arr對象出了(NSMutableArray *)array沒有在被持有,則在autoreleasePool清空的時候(之后arr引用計數(shù)為0)arr被釋放內(nèi)存痊远。所以一個autorelease對象垮抗,當(dāng)被__weak修飾的變量持有時,它的生命周期就是所在autoreleasePool結(jié)束前的那段時間碧聪;
b. 接下來看一下- (id)autorelease函數(shù)的底層實現(xiàn):

id *objc_autorelease(id obj)
{
 return AutoreleasePoolPage::autorelease(obj);
}

AutoreleasePoolPage即自動釋放池的底層實現(xiàn)冒版,可以看我的博客AutoreleasePool,其實就是將需要延遲釋放的對象放入了一個鏈表逞姿,程序中可能存在很多個自動釋放池(每次會使用棧頂?shù)淖詣俞尫懦胤湃耄?br> c. 那么辞嗡,實際結(jié)果真是這樣的嗎?哼凯?欲间?(使用函數(shù)打印楚里、TLS)
我們可以使用clang命令對文件進行編譯断部,查看ARC之后的代碼是怎么樣的;另外通常情況需要制定引用的Framework(控制臺程序通常不需要):

xcrun -sdk iphonesimulator10.2 clang -S -fobjc-arc -emit-llvm main.m -o main.ll -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework

使用上述命令可以生成編譯器ARC之后的代碼班缎,

int main(int argc, const char * argv[]) {
    @autoreleasepool{
        NSObject *obj = [NSObject new];
    }
    return 0;
}

下面是生成的底層代碼:

define i32 @main(i32, i8**) #0 {
  %3 = alloca i32, align 4 //在棧上分配變量名內(nèi)存
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca %0*, align 8 //實際上這里就是NSObject *obj變量定義處
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %7 = call i8* @objc_autoreleasePoolPush() #2 //創(chuàng)建一個自動釋放池
  %8 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8 //加載類對象地址(這里是NSObject元類) 
  %9 = load i8*, i8** @OBJC_SELECTOR_REFERENCES_, align 8, !invariant.load !8 //加載SEL地址蝴光,這里是new函數(shù)
  %10 = bitcast %struct._class_t* %8 to i8* //類型轉(zhuǎn)換
  %11 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %10, i8* %9) //[NSObject new]
  %12 = bitcast i8* %11 to %0*
  store %0* %12, %0** %6, align 8 //賦值給NSObject *obj,定義在開始處
  %13 = bitcast %0** %6 to i8**
  call void @objc_storeStrong(i8** %13, i8* null) #2 //釋放
  call void @objc_autoreleasePoolPop(i8* %7) //銷毀自動釋放池
  ret i32 0
}

第一步达址,在棧上先分配變量名所需要的內(nèi)存(alloca x, x)蔑祟;
第二步,將一個自動釋放池壓入棧頂(@ objc_autoreleasePoolPush())沉唠;
第三步疆虚,中間一些代碼就是獲取獲取NSObject元類對象以及加載new 函數(shù)的SEL地址,然后調(diào)用objc_msgSend()進行實例化;
第四步径簿,使用完之后調(diào)用objc_storeStrong(src, value)罢屈,這個函數(shù)可以用來保存一個新對象,當(dāng)然如果傳入的value = null篇亭,也可以用來釋放原來的對象缠捌,下面是objc_storeStrong(,)內(nèi)部實現(xiàn):

void objc_storeStrong(id *location, id obj)
{
    id prev = *location; //暫存現(xiàn)在對象
    if (obj == prev) {
        return;
    }
    objc_retain(obj); //持有新對象
    *location = obj;  //指向新對象
    objc_release(prev); //釋放舊對象
}

當(dāng)然,本文這里傳入的是null译蒂,所以就是使用完就立即釋放掉了曼月;
而當(dāng)源碼中實例化對象方法改成這樣時:

   NSMutableArray *obj = [NSMutableArray array];

改變實例化方式,array內(nèi)部會調(diào)用autorelease:

  %7 = call i8* @objc_autoreleasePoolPush() #2
  %11 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend
  %12 = call i8* @objc_retainAutoreleasedReturnValue(i8* %11) #2
...
  call void @objc_storeStrong(i8** %14, i8* null) #2
  call void @objc_autoreleasePoolPop(i8* %7)

可以看出柔昼,雖然這里看不出array里調(diào)用了autorelease(下面會驗證),但是對array返回的對象調(diào)用了objc_retainAutoreleasedReturnValue哑芹,這個其實是clang編譯器對其進行了優(yōu)化(只針對64位系統(tǒng)),會去一個叫TLS(Thread Local Storage)的地方(實際上是一個放在線程上的字典局部變量)查看該返回對象是否做了標記捕透,這樣可以節(jié)約retain和release的開銷绩衷;

三、放入AutoreleasePool時機

其實上面已經(jīng)說明了激率,通常系統(tǒng)提供的實例化方法如new/alloc/copy/mutablecopy等方法返回的實例對象是不會放進自動釋放池咳燕,而像[NSMutableArray array]等簡便函數(shù)構(gòu)造器會內(nèi)部調(diào)用autotorelase
我們可以使用下面的函數(shù)和命令驗證:

void _objc_autoreleasePoolPrint();

《Objective-C 高級編程》中有介紹該函數(shù)乒躺,這是一個非公開的私有函數(shù)招盲,可以打印出自動釋放池里的內(nèi)容,具體的用法現(xiàn)在文件開頭聲明該外部函數(shù)

extern void _objc_autoreleasePoolPrint(void);

下面看一下[NSMutableArray new]和[NSMutableArray array]下自動釋放池的情況:
[NSMutableArray new]下自動釋放池情況:

objc[55819]: [0x102003000]  ................  PAGE  (hot) (cold)
objc[55819]: [0x102003038]  ################  POOL 0x102003038
objc[55819]: ##############

[NSMutableArray array]下:

objc[56005]: [0x101004000]  ................  PAGE  (hot) (cold)
objc[56005]: [0x101004038]  ################  POOL 0x101004038
objc[56005]: [0x101004040]       0x100621490  __NSArrayM
objc[56005]: ##############

可以看出嘉冒,在+(NSMutableArray *)array下一個__NSArrayM被加入自動釋放池曹货,所以前面的理論是正確的。

四讳推、應(yīng)用場景

通常情況下我們不需要Autorelease也沒什么關(guān)系顶籽,但是有時候理解了底層原理,我們就可以理解了一些大家常用的技巧:

for (int i=0; i<1000000; i++)
{
  @autoreleasepool{
        //實例化臨時對象
    }
}

這常常用來解決內(nèi)存峰值問題银觅,但是其實如果里邊不包含array這種類型的函數(shù)礼饱,即使實例化大量對象也是不會導(dǎo)致內(nèi)存峰值出現(xiàn)的,如:

 for (int i=0; i<100000000; i++){
        NSMutableArray *obj = [NSMutableArray new];
        NSNumber *num = [[NSNumber alloc] initWithInt:i];
        [obj addObject:num];
        NSLog(@"%@", obj);
    }

五究驴、小結(jié)

  1. Autorelease用來延遲釋放對象镊绪,該對象釋放的時機在所在自動釋放池情況時;
  2. 使用new/alloc/copy/mutablecopy等函數(shù)實例化的對象不會放入自動釋放池洒忧,相反蝴韭,用其他簡便構(gòu)造器獲(如+(NSMutableArray *)array)得到的實例則會放入自動釋放池;
  3. 上面講的是通常情況便于理解熙侍,其實編譯器內(nèi)部還對Autorelease進行了優(yōu)化榄鉴,用于降低內(nèi)存管理的開銷履磨。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庆尘,隨后出現(xiàn)的幾起案子蹬耘,更是在濱河造成了極大的恐慌,老刑警劉巖减余,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件综苔,死亡現(xiàn)場離奇詭異,居然都是意外死亡位岔,警方通過查閱死者的電腦和手機如筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抒抬,“玉大人杨刨,你說我怎么就攤上這事〔两#” “怎么了妖胀?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惠勒。 經(jīng)常有香客問我赚抡,道長,這世上最難降的妖魔是什么纠屋? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任涂臣,我火速辦了婚禮,結(jié)果婚禮上售担,老公的妹妹穿的比我還像新娘赁遗。我一直安慰自己,他們只是感情好族铆,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布岩四。 她就那樣靜靜地躺著,像睡著了一般哥攘。 火紅的嫁衣襯著肌膚如雪剖煌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天献丑,我揣著相機與錄音末捣,去河邊找鬼。 笑死创橄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莽红。 我是一名探鬼主播妥畏,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼邦邦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了醉蚁?” 一聲冷哼從身側(cè)響起燃辖,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎网棍,沒想到半個月后黔龟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡滥玷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年氏身,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惑畴。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛋欣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出如贷,到底是詐尸還是另有隱情陷虎,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布杠袱,位于F島的核電站尚猿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏楣富。R本人自食惡果不足惜谊路,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菩彬。 院中可真熱鬧缠劝,春花似錦、人聲如沸骗灶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耙旦。三九已至脱羡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間免都,已是汗流浹背锉罐。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绕娘,地道東北人脓规。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像险领,于是被迫代替她去往敵國和親侨舆。 傳聞我的和親對象是個殘疾皇子秒紧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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