Objective-C高級編程讀書筆記之內(nèi)存管理

Objective-C高級編程 iOS與OS X多線程和內(nèi)存管理

Objective-C高級編程讀書筆記三部曲已經(jīng)寫完, 另外兩篇如下 :
Objective-C高級編程讀書筆記之blocks
Objective-C高級編程讀書筆記之GCD


自動引用計數(shù)(ARC, Automatic Reference Counting)

目錄

  1. 什么是自動引用計數(shù)
  2. 內(nèi)存管理的思考方式
  3. autorelease
  4. 所有權(quán)修飾符介紹
  5. ARC規(guī)則
  6. ARC實(shí)現(xiàn)(所有權(quán)修飾符作用詳解)
  7. 如何獲取引用計數(shù)值
  8. 總結(jié)

1. 什么是自動引用計數(shù)

編譯器自動幫你在合適的地方插入retain/release代碼, 不用你手動輸入.

2. 內(nèi)存管理的思考方式

  • 自己生成的對象, 自己持有.
  • 非自己生成的對象, 自己也能持有.
  • 不再需要自己持有對象時釋放.
  • 非自己持有的對象無法釋放.
對象操作 Objective-C方法
生成并持有對象 alloc/new/copy/mutableCopy等方法
持有對象 retain方法
釋放對象 release方法
廢棄對象 dealloc方法

非自己生成的對象, 自己也能持有

如 :

id obj = [NSMutableArray array]; // 取得對象存在, 但自己并不持有對象
[obj retain]; // 自己持有對象

不再需要自己持有對象時釋放

如 :

id obj = [[NSObject alloc] init]; // 自己持有對象
[obj autorelease]; // 取得的對象存在, 但自己不持有對象

無法釋放非自己持有的對象

如 :

id obj = [NSMutableArray array]; // 取得對象存在, 但自己并不持有對象
[obj release]; // 釋放了非自己持有的對象!會導(dǎo)致應(yīng)用程序崩潰

永遠(yuǎn)不要去釋放非自己持有的對象!

- 在Objective-C的對象中存有引用計數(shù)這一整數(shù)值
- 調(diào)用alloc/retain方法后, 引用計數(shù)+1
- 調(diào)用release后, 引用計數(shù)-1
- 引用計數(shù)為0時, 調(diào)用dealloc廢棄對象

GNUstep將引用計數(shù)保存在對象占用內(nèi)存塊頭部的結(jié)構(gòu)體(struct obj_layout)變量(retained)中, 而蘋果的實(shí)現(xiàn)則是采用散列表(引用計數(shù)表)來管理引用計數(shù).

GNUstep的好處 :

  • 少量代碼即可完成.
  • 能夠統(tǒng)一管理引用計數(shù)用內(nèi)存塊與對象用內(nèi)存塊.

蘋果的好處 :

  • 對象用內(nèi)存塊的分配無需考慮內(nèi)存塊頭部.
  • 引用計數(shù)表各記錄中存有內(nèi)存塊地址, 可從各個記錄追溯到各對象的內(nèi)存塊.

3. autorelease

每一個RunLoop對應(yīng)一個線程, 每個線程維護(hù)一個autoreleasepool.

RunLoop開始 -> 創(chuàng)建autoreleasepool -> 線程處理事件循環(huán) -> 廢棄autoreleasepool -> RunLoop結(jié)束 -> 等待下一個Loop開始

可以用以下函數(shù)來打印AutoreleasePoolPage的情況

// 函數(shù)聲明 
extern void _objc_autoreleasePoolPrint();
// autoreleasepool 調(diào)試用輸出開始
_objc_autoreleasePoolPrint();

NSAutoreleasePool類的autorelease方法已被重載, 因此調(diào)用NSAutoreleasePool對象的autorelease會報錯.


4. 所有權(quán)修飾符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong

ARC中, id及其他對象默認(rèn)就是__strong修飾符修飾
MRC中, 使用__strong修飾符, 不必再次鍵入retain/release. 持有強(qiáng)引用的變量超出其作用域時被廢棄, 隨著強(qiáng)引用的失效, 引用的對象會隨之釋放.

__weak

解決循環(huán)引用問題. 并且弱引用的對象被廢棄時, 則此弱引用將自動失效并等于nil

通過__weak變量訪問對象實(shí)際上必定是訪問注冊到autoreleasepool的對象, 因為該修飾符只持有對象的弱引用, 在訪問對象的過程中, 該對象可能被廢棄, 如果把要訪問的對象注冊到autoreleasepool中, 那么在block結(jié)束之前都能確保該對象存在.

__unsafe_unretained

不安全的修飾符, 附有該修飾符的變量不屬于編譯器的內(nèi)存管理對象. 該修飾符與__weak一樣, 是弱引用, 并不能持有對象.并且訪問該修飾符的變量時如果不能確保其確實(shí)存在, 則應(yīng)用程序會崩潰!

__autoreleasing

對象賦值給__autoreleasing修飾的變量相當(dāng)于MRC下手動調(diào)用autorelease方法.可理解為, ARC下用@autoreleasepool block代替NSAutoreleasePool類, 用__autoreleasing修飾符的變量代替autorelease方法.
但是, 顯式使用__autoreleasing修飾符跟__strong一樣罕見,

ps : id的指針或者對象的指針會被隱式附上__autoreleasing修飾符, 如 :
id *obj == id __autoreleasing *obj;
NSObject **obj == NSObject * __autoreleasing *obj;


編譯器特性

編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始, 如果不是則自動將返回值對象注冊到autoreleasepool(init方法返回值對象不注冊到autoreleasepool), 詳情見下面ARC規(guī)則之<須遵循內(nèi)存管理的方法命名規(guī)則>


5. ARC規(guī)則

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 須遵循內(nèi)存管理的方法命名規(guī)則
  • 不要顯式調(diào)用dealloc(即不能手動調(diào)用的dealloc方法, 可以重載)
  • 使用@autoreleasepool block代替NSAutoreleasePool
  • 不能使用區(qū)域(NSZone)
  • 對象型變量不能作為C語言結(jié)構(gòu)體(struct/union)的成員
  • 顯式轉(zhuǎn)換"id"和"void *"

不能使用NSAllocateObject/NSDeallocateObject

alloc實(shí)現(xiàn)實(shí)際上是通過直接調(diào)用NSAllocateObject函數(shù)來生成并持有對象, ARC下禁止使用NSAllocateObject函數(shù)與NSDeallocateObject函數(shù).

須遵循內(nèi)存管理的方法命名規(guī)則

只有作為alloc/new/copy/mutableCopy方法的返回值取得對象,才能自己生成并持有對象, 其余情況均為"取得非自己生成并持有的對象"..以下為ARC下編譯器偷偷幫我們實(shí)現(xiàn)的事.

+ (Person *)newPerson {
    Person *person = [[Person alloc] init];
    return person;
    /* 該方法以new開始, 所以直接返回對象本身, 無需調(diào)用autorelease */
}

+ (Person *)onePerson {
    Person *person = [[Person alloc] init];
    return person;
    /* 該方法不以alloc/new/copy/mutableCopy開始, 所以返回的是[person autorelease] */
}

- (void)doSomething {
    Person *personOne = [Person newPerson];
    // ...
    Person *personTwo = [Person onePerson];
    // ...
    
    /*  當(dāng)方法結(jié)束時, ARC會自動插入[personOne release]. <想想是為什么?> */
}

ARC下還有一條命名規(guī)則

  • init

以init名稱開始的方法必須是實(shí)例方法(對象方法), 并且要返回對象, 返回的對象不注冊到autoreleasepool上. 實(shí)際上只是對alloc方法返回值的對象做初始化處理并返回該對象.

不能使用區(qū)域(NSZone)

在現(xiàn)在的運(yùn)行時系統(tǒng)中, NSZone已被忽視

顯式轉(zhuǎn)換 id 和 void *

  • __bridge轉(zhuǎn)換
  • __bridge_retained轉(zhuǎn)換
  • __bridge_transfer轉(zhuǎn)換

__bridge轉(zhuǎn)換

單純地轉(zhuǎn)換, 不安全.

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

__bridge_retained轉(zhuǎn)換

可使要轉(zhuǎn)換賦值的變量也持有所賦值的對象, 即

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
// 相當(dāng)于加上 [(id)p retain]; 

則obj與p同時持有該對象

__bridge_transfer轉(zhuǎn)換

與__bridge_retained相反, 被轉(zhuǎn)換的變量所持有的對象在該變量被賦值給轉(zhuǎn)換目標(biāo)變量后隨之釋放.

id obj = [[NSObject alloc] init];
void *p = (__bridge_transfer void *)obj;
// 相當(dāng)于加上 [(id)p retain]; [obj release];

小結(jié)

__ bridge_retained轉(zhuǎn)換與retain類似, __ bridge_transfer轉(zhuǎn)換與release類似. 該兩種轉(zhuǎn)換多用于Foundation對象與Core Foundation對象之間的轉(zhuǎn)換


屬性

@property (nonatomic, strong) NSString *name;

在ARC下, 以下可作為這種屬性聲明中使用的屬性來用.

屬性聲明的屬性 所有權(quán)修飾符
assign __unsafe_unretained修飾符
copy __strong修飾符(但是賦值的是被復(fù)制的對象)
retain __strong修飾符
strong __strong修飾符
unsafe_unretained __unsafe_unretained修飾符
weak __weak修飾符

以上各種屬性賦值給指定的屬性中就相當(dāng)于賦值給附加各屬性對應(yīng)的所有權(quán)修飾符的變量中.


6. ARC實(shí)現(xiàn)

ARC是由編譯器+運(yùn)行時庫共同完成的.

__strong修飾符

{
    id __strong obj = [[NSObject alloc] init];
}
可轉(zhuǎn)換為以下代碼
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
{
    id __strong obj = [NSMutableArray array];
}
可轉(zhuǎn)換為以下代碼
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

objc_retainAutoreleasedReturnValue函數(shù)主要用于最優(yōu)化程序運(yùn)行. 它是用來持有返回注冊在autoreleasepool中對象的方法.這個函數(shù)是成對的, 另外一個objc_autoreleaseReturnValue函數(shù)則用于alloc/new/copy/mutableCopy方法以外的類方法返回對象的實(shí)現(xiàn)上, 如下 :

+ (id)array
{
    return [[NSMutableArray alloc] init];
}
可轉(zhuǎn)換為以下代碼
+ (id)array
{
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    return objc_autoreleaseReturnValue(obj);
}

那么objc_autoreleaseReturnValue函數(shù)和objc_retainAutoreleasedReturnValue函數(shù)有什么用?

省略autoreleasepool注冊

可以這樣來總結(jié) :
如果編譯器檢測到調(diào)用autorelease之后又緊接著調(diào)用retain的話, 就省略掉autorelease方法的調(diào)用. 通過objc_autoreleaseReturnValue函數(shù)和objc_retainAutoreleasedReturnValue函數(shù)將對象直接傳遞, 忽略掉多余的操作, 優(yōu)化程序.

__weak修飾符

  • 若附有__weak修飾符的變量所引用的對象被廢棄, 則將nil賦值給該變量.
  • 使用附有__weak修飾符的變量, 即是使用注冊到autoreleasepool中的對象.
{
    id __weak obj1 = obj;
}

可轉(zhuǎn)換為以下代碼
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

也可轉(zhuǎn)換為以下代碼
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

訪問__weak變量時, 相當(dāng)于訪問注冊到autoreleasepool的對象

{
    id __weak obj1 = obj;
    NSLog(@"%@", obj1);
}

可轉(zhuǎn)換為以下代碼
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1); 
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);

// objc_loadWeakRetained函數(shù)取出附有__weak修飾符變量所引用對象并retain

需要注意的是, 通過__weak變量訪問所引用的對象幾次, 對象就被注冊到autoreleasepool里幾次. (將附有__weak修飾符的變量賦值給附有__strong修飾符的變量后再使用可避免此問題)

__autoreleasing修飾符

將對象賦值給附有__autoreleasing修飾符的變量等同于MRC下調(diào)用對象的autorelease方法.

@autoreleasepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}

可轉(zhuǎn)換為以下代碼
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

那么調(diào)用alloc/new/copy/mutableCopy以外的方法會怎樣呢?

@autoreleasepool{
    id __autoreleasing obj = [NSMutableArray array];
}

可轉(zhuǎn)換為以下代碼
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

可見注冊autorelease的方法沒有改變, 仍是objc_autorelease函數(shù)

7. 如何獲取引用計數(shù)值

獲取引用數(shù)值的函數(shù)
uinptr_t _objc_rootRetainCount(id obj)

- (NSUInteger)retainCount;
該方法返回的引用計數(shù)不一定準(zhǔn)確, 因為有時系統(tǒng)會優(yōu)化對象的釋放行為, 在保留計數(shù)為1的時候就把它回收. 所以你用這個方法打印出來的引用計數(shù)可能永遠(yuǎn)不會出現(xiàn)0. 我們不應(yīng)該根據(jù)retainCount來調(diào)試程序!!


8. 總結(jié)

我們現(xiàn)在的工程幾乎都運(yùn)行在ARC下, 所以大部分內(nèi)存管理代碼都不需要我們自己寫, 而由編譯器幫我們搞定. 所以在ARC下我們只需要知道怎樣不要去破壞這個生態(tài)即可

  • 避免循環(huán)引用(使用__weak修飾符)
  • 遵循ARC方法命名規(guī)則
  • 適時清空指針(賦值nil即可, 避免野指針錯誤)
  • 如用到Core Foundation對象, 則在dealloc方法中釋放
  • 在dealloc方法中只釋放引用并移除監(jiān)聽(不能在dealloc中開啟異步任務(wù))
  • 對于內(nèi)存開銷較大的資源, 如file descriptor, socket, 大塊內(nèi)存等應(yīng)在不需要使用的時候調(diào)用close方法釋放掉而不是在dealloc中處理.
  • 適當(dāng)使用@autoreleasepool block來降低內(nèi)存峰值(之前我寫的一篇文章中有demo)
  • 必要時開啟"僵尸對象"調(diào)試內(nèi)存管理問題

歡迎大家關(guān)注@Jerry4me, 關(guān)注菜鳥成長_. 我會不定時更新一些學(xué)習(xí)心得與文章.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評論 2 374

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