Objective-C高級編程讀書筆記三部曲已經寫完, 另外兩篇如下 :
Objective-C高級編程讀書筆記之blocks
Objective-C高級編程讀書筆記之GCD
自動引用計數(shù)(ARC, Automatic Reference Counting)
目錄
- 什么是自動引用計數(shù)
- 內存管理的思考方式
- autorelease
- 所有權修飾符介紹
- ARC規(guī)則
- ARC實現(xiàn)(所有權修飾符作用詳解)
- 如何獲取引用計數(shù)值
- 總結
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ù)有什么用?
可以這樣來總結 :
如果編譯器檢測到調用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, 關注菜鳥成長_. 我會不定時更新一些學習心得與文章.