Objective-C高級編程讀書筆記之內存管理

Objective-C高級編程 iOS與OS X多線程和內存管理

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


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

目錄

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

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

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

2. 內存管理的思考方式

  • 自己生成的對象, 自己持有.
  • 非自己生成的對象, 自己也能持有.
  • 不再需要自己持有對象時釋放.
  • 非自己持有的對象無法釋放.
對象操作 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]; // 釋放了非自己持有的對象!會導致應用程序崩潰

永遠不要去釋放非自己持有的對象!

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

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

GNUstep的好處 :

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

蘋果的好處 :

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

3. autorelease

每一個RunLoop對應一個線程, 每個線程維護一個autoreleasepool.

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

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

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

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


4. 所有權修飾符

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong

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

__weak

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

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

__unsafe_unretained

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

__autoreleasing

對象賦值給__autoreleasing修飾的變量相當于MRC下手動調用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ī)則之<須遵循內存管理的方法命名規(guī)則>


5. ARC規(guī)則

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

不能使用NSAllocateObject/NSDeallocateObject

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

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

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

+ (Person *)newPerson {
    Person *person = [[Person alloc] init];
    return person;
    /* 該方法以new開始, 所以直接返回對象本身, 無需調用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];
    // ...
    
    /*  當方法結束時, ARC會自動插入[personOne release]. <想想是為什么?> */
}

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

  • init

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

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

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

顯式轉換 id 和 void *

  • __bridge轉換
  • __bridge_retained轉換
  • __bridge_transfer轉換

__bridge轉換

單純地轉換, 不安全.

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

__bridge_retained轉換

可使要轉換賦值的變量也持有所賦值的對象, 即

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

則obj與p同時持有該對象

__bridge_transfer轉換

與__bridge_retained相反, 被轉換的變量所持有的對象在該變量被賦值給轉換目標變量后隨之釋放.

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

小結

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


屬性

@property (nonatomic, strong) NSString *name;

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

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

以上各種屬性賦值給指定的屬性中就相當于賦值給附加各屬性對應的所有權修飾符的變量中.


6. ARC實現(xiàn)

ARC是由編譯器+運行時庫共同完成的.

__strong修飾符

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

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

+ (id)array
{
    return [[NSMutableArray alloc] init];
}
可轉換為以下代碼
+ (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注冊

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

__weak修飾符

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

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

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

訪問__weak變量時, 相當于訪問注冊到autoreleasepool的對象

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

可轉換為以下代碼
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下調用對象的autorelease方法.

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

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

那么調用alloc/new/copy/mutableCopy以外的方法會怎樣呢?

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

可轉換為以下代碼
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ù)不一定準確, 因為有時系統(tǒng)會優(yōu)化對象的釋放行為, 在保留計數(shù)為1的時候就把它回收. 所以你用這個方法打印出來的引用計數(shù)可能永遠不會出現(xiàn)0. 我們不應該根據(jù)retainCount來調試程序!!


8. 總結

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

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

歡迎大家關注@Jerry4me, 關注菜鳥成長_. 我會不定時更新一些學習心得與文章.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌针贬,老刑警劉巖忍宋,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件山害,死亡現(xiàn)場離奇詭異言疗,居然都是意外死亡,警方通過查閱死者的電腦和手機柬甥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門饮六,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苛蒲,你說我怎么就攤上這事卤橄。” “怎么了臂外?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵窟扑,是天一觀的道長。 經常有香客問我漏健,道長嚎货,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任蔫浆,我火速辦了婚禮殖属,結果婚禮上,老公的妹妹穿的比我還像新娘瓦盛。我一直安慰自己洗显,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布原环。 她就那樣靜靜地躺著挠唆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘱吗。 梳的紋絲不亂的頭發(fā)上损搬,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音柜与,去河邊找鬼巧勤。 笑死,一個胖子當著我的面吹牛弄匕,可吹牛的內容都是我干的颅悉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼迁匠,長吁一口氣:“原來是場噩夢啊……” “哼剩瓶!你這毒婦竟也來了?” 一聲冷哼從身側響起城丧,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤延曙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亡哄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枝缔,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了愿卸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灵临。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趴荸,靈堂內的尸體忽然破棺而出儒溉,到底是詐尸還是另有隱情,我是刑警寧澤发钝,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布顿涣,位于F島的核電站,受9級特大地震影響酝豪,放射性物質發(fā)生泄漏园骆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一寓调、第九天 我趴在偏房一處隱蔽的房頂上張望锌唾。 院中可真熱鬧,春花似錦夺英、人聲如沸晌涕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽余黎。三九已至,卻和暖如春载萌,著一層夾襖步出監(jiān)牢的瞬間惧财,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工扭仁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垮衷,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓乖坠,卻偏偏與公主長得像搀突,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子熊泵,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容