自動(dòng)引用計(jì)數(shù)
看完了 Objective-C 高級(jí)編程 第一章烦粒,不得不吐槽休溶,翻譯這本書的作者的 English Level 和 Chinese Level 都和我差不多啊,看起來真吃力扰她。兽掰。。
這一章的內(nèi)容徒役,實(shí)際中也很少會(huì)用到孽尽,然并卵。寫下自己的筆記和一些體會(huì)忧勿,遇到問題時(shí)再回頭看看吧杉女。
文中常使用的 3 個(gè)函數(shù):
extern void _objc_autoreleasePoolPrint(); // 在 iOS 不可用瞻讽,在 OSX 上可用
extern uintptr_t _objc_rootRetainCount(id obj); // 在 iOS 和 OSX 都可用
CFGetRetainCount(CFTypeRef ref); // 在 iOS 和 OSX 都可用
內(nèi)存管理的思考方式
個(gè)人觀點(diǎn),這種思考方式反而帶來了更多糾結(jié)的地方熏挎,不用細(xì)究它速勇。
- 自己生成的對(duì)象,自己持有婆瓜。
- 非自己生成的對(duì)象快集,自己也能持有。
- 不再需要自己持有的對(duì)象時(shí)釋放廉白。
- 非自己持有的對(duì)象無法釋放
自己生成的對(duì)象自己持有个初,是指使用以 alloc
、new
猴蹂、copy
院溺、mutableCopy
開頭的方法生成的對(duì)象。這句話能只有在 OS X 中使用 alloc
時(shí)得到了驗(yàn)證磅轻,代碼參考 1-內(nèi)存管理思考方式珍逸。個(gè)人認(rèn)為吧,現(xiàn)在蘋果在實(shí)現(xiàn)函數(shù)返回值的時(shí)候都已經(jīng)進(jìn)行了優(yōu)化聋溜,不像在 MRC 時(shí)代需要先將返回值加入到 autoreleasepool
再重新 retain
谆膳,而是直接將返回值傳遞給調(diào)用者。具體參考 objc_autoreleaseReturnValue()
和 objc_retainAutoreleasedReturnValue()
方法和圖1撮躁。
// 只在 OSX 中有效
MyObject *a = [MyObject allocMyObject]; // a 的引用計(jì)數(shù)為 1
MyObject *b = [MyObject allocmyObject]; // b 的引用計(jì)數(shù)為 2
引用計(jì)數(shù)的實(shí)現(xiàn)
不像 GNUstep 將引用計(jì)數(shù)保存到對(duì)象占用的內(nèi)存塊頭部漱病,Apple 通過引用計(jì)數(shù)表實(shí)現(xiàn)對(duì)象的引用計(jì)數(shù)。GUNstep 的實(shí)現(xiàn)很取巧把曼,Orz mark 一下杨帽。
struct obj_layout {
NSUInteger retained;
}
+ (id) alloc {
int size = sizeof(struct obj_layout) + 對(duì)象大小;
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
return (id)(p + 1);
}
// 得到引用計(jì)數(shù)
inline NSUInteger NSExtraRefCount(id anObject) {
return ((struct obj_layout *)anObject)[-1].retained;
}
autorelease
注意:在大量產(chǎn)生
autorelease
對(duì)象時(shí),只要autoreleasepool
沒有廢棄嗤军,那么生成的對(duì)象就不能被釋放菩佑,可以使用下面的代碼去避免它孕惜。在 ARC 中,如果對(duì)象變量是__strong
修飾符隘截,那么在變量出了作用域就會(huì)調(diào)用release
咧党,所以一般不用擔(dān)心泳挥。
for (int i = 0; i < 圖像數(shù); ++i) {
@autoreleasepool {
// 讀入圖片惊暴,產(chǎn)生大量 autorelease 對(duì)象
}
// autorelease 對(duì)象被 release
}
GNUstep 在實(shí)現(xiàn) autorelease
上穷劈,實(shí)際上使用了 IMP Caching,代碼如下冤荆。
id autorelease_class = [NSAutoreleasePool class]
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// 實(shí)際上方法調(diào)用就是使用緩存的結(jié)果值
-(id)autorelease {
(*autorelease_imp)(autorelease_class, autorelease_sel, self);
}
所有權(quán)修飾符
所有權(quán)修飾符是為了處理 id
類型或?qū)ο笾羔橆愋偷摹RC 有效時(shí)权纤,id
類型和對(duì)象指針類型必須附加所有權(quán)修飾符钓简。
// 對(duì)于 id 和普通對(duì)象類型乌妒,修飾符可以放在任何位置,XCode 編譯器都能解釋外邓。
[__strong] id [__strong] a [__strong];
[__strong] NSObject [__strong] * [__strong] a [__strong];
// 但是對(duì)于 id* 和對(duì)象指針的指針撤蚊,修飾符不能放在變量前
[__strong] id [__storng] * [不能] a [__strong];
// 出現(xiàn)提示錯(cuò)誤為:
// '__strong' only applies to Objective-C object or block pointer types; type here is 'NSObject *__strong *' 。
// 這條警告對(duì)所有權(quán)修飾符的作用已經(jīng)說明得很清楚了损话,它修飾的是對(duì)象侦啸,它指出變量在被賦值和離開作用域時(shí)應(yīng)該怎樣處理對(duì)象。
__strong
__strong
是 id
類型和對(duì)象指針的默認(rèn)修飾符丧枪,但是對(duì)多級(jí)指針或 id *
類型必須明確指出所屬修飾符光涂。附有 __strong
修飾符的變量在超出變量作用域時(shí)(即在該變量被廢棄時(shí)),會(huì)對(duì)其指向的對(duì)象發(fā)送 release
消息拧烦。
__weak
__weak
系統(tǒng)的實(shí)現(xiàn)有一個(gè)奇特的地方忘闻,就是在使用 __weak
引用的對(duì)象時(shí),都會(huì)先將對(duì)象 retain
成 __strong
類型再使用恋博,代碼參考 4-弱引用齐佳。
id __strong a = [NSObject new];
id __weak b = a;
NSLog(@"%lu", _objc_rootRetainCount(a)); // 輸出 1
NSLog(@"%lu", _objc_rootRetainCount(b)); // 輸出 2
// 最后一條語句相當(dāng)于:
// id __strong tmp = b;
// NSLog(@"%lu", _objc_rootRetainCount(tmp)); // 輸出 2
// [tmp release]
// 書中說,__weak 附加的變量使用時(shí)债沮,tmp 的所有權(quán)修飾符是 __autoreleasing炼吴,
// 但根據(jù)測(cè)試結(jié)果,自動(dòng)釋放池中并沒有增加對(duì)象疫衩,有可能是 Apple 改了實(shí)現(xiàn)吧硅蹦。
// 這也提醒我們?cè)谑褂?__weak 前要使用 __strong 先綁定一下,不要讓系統(tǒng)去綁定隧土。
在使用 __weak
時(shí)提针,實(shí)際上還會(huì)調(diào)用下面兩個(gè)函數(shù),參見代碼4-弱引用:
// id __strong a = [[MyObject alloc] init];
// id __weak b = a; 時(shí)會(huì)調(diào)用該函數(shù)曹傀,返回 NO辐脖,將在運(yùn)行時(shí)發(fā)生異常終止
-(BOOL)allowsWeakReference;
// 每次把 __weak 的變量賦值給 __strong, __weak, __unsafe__unretained 和 __autoreleasing
// 修飾的變量都會(huì)調(diào)用該函數(shù),由上面代碼也能聯(lián)想到皆愉,每次使用 __weak 的對(duì)象時(shí)也會(huì)調(diào)用該函數(shù)(每次都要生成一個(gè)臨時(shí)對(duì)象)嗜价。
// 該函數(shù)返回 NO,便會(huì)給想要賦值的變量賦為 nil幕庐。
// 比如 id __weak a; 現(xiàn)在該函數(shù)返回 NO久锥,則 id __strong b = a; 時(shí),b 的值為 nil异剥。
-(BOOL)retainWeakReference; // 要調(diào)用 [super retainWeakReference]
__autoreleasing
二級(jí)指針并不會(huì)保證初始化為 nil
瑟由,應(yīng)該顯式初始化。現(xiàn)在 XCode 對(duì)本地二級(jí)指針必須嚴(yán)格指定修飾符冤寿,而對(duì)于函數(shù)參數(shù)傳遞的二級(jí)指針默認(rèn)修飾符為 __autoreleasing
歹苦。如果將函數(shù)參數(shù)傳遞的二級(jí)指針修飾符指定為 __strong
青伤,則對(duì)象的生命周期由指向 __strong
變量的作用域決定,參考2-二級(jí)指針參數(shù)傳遞殴瘦。
我就說一句:不要管
__autoreleasing
了狠角。在 ARC 里面它沒什么用,除了 Apple 腦子有病 在函數(shù)參數(shù)中將 pointer-to-pointer 的修飾符指定為__autoreleasing
蚪腋,參數(shù)傳遞明明可以用__strong
實(shí)現(xiàn)丰歌。
void fun(id *a) {
// id *a 默認(rèn)為:id __autoreleasing *a; *a 的對(duì)象加入自動(dòng)釋放池
*a = [NSObject new];
}
id __strong a = nil;
fun(&a);
// 會(huì)被轉(zhuǎn)換為:
// id __autoreleasing * tmp = &a; (具體實(shí)現(xiàn)不清楚,因?yàn)闇y(cè)試結(jié)果顯示沒有把對(duì)象 a 加入到 autoreleaePool)
// fun(&tmp);
// a = *tmp;
__unsafe_unreatined
相當(dāng)于 C 語言中的直接指針賦值屉凯。不像 __weak
立帖,對(duì)象銷毀后指針不會(huì)被置為 nil
。現(xiàn)在已不考慮該修飾符神得。
Toll-Free Bridge
Toll-Free Bridge 修飾符要放到數(shù)據(jù)類型前面厘惦,記為:(__bridge_transfer NSObject *)
__bridge_retained
用于將 Objective-C 對(duì)象轉(zhuǎn)換為 Core Foundation 對(duì)象。
CFTypeRef CFBridgingRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}
__bridge_transfer
用于將 Core Foundation 對(duì)象轉(zhuǎn)換為 Objective-C 對(duì)象哩簿。
id CFBridgingRelease(CFTypeRef X) {
return (__bridge_transfer id)X;
}
__bridge
小心使用宵蕉。使用 __bridge
代替 __bridge_retained
可能會(huì)出現(xiàn)懸垂指針(野指針)。
CFMutableArrayRef cfObject = NULL;
{
id obj = [[NSMutableArray alloc] init];
cfObject = (__bridge CFMutableArrayRef) obj; // 這里應(yīng)該使用 __bridge_retained
}
// cfObject 在這里成為了野指針
使用 __bridge
代替 __bridge_transfer
可能會(huì)出現(xiàn)內(nèi)存泄露节榜。
{
CFMutableArrayRef cfObject = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
id obj = (__bridge id) cfObject; // 這里應(yīng)該使用 __bridge_transfer
// 因?yàn)?obj 是 __strong 修飾符的羡玛,所以會(huì)發(fā)生強(qiáng)引用
// 不發(fā)生內(nèi)存泄露的話這里應(yīng)該手動(dòng)釋放 ofObject:CFRelease(cfObject) 或使用 __bridge_transfer
}
// 內(nèi)存泄露
數(shù)組
靜態(tài)數(shù)組
靜態(tài)數(shù)組:指大小確定的數(shù)組,即 id objs[10]宗苍;它的使用和普通的變量沒有區(qū)別稼稿。
動(dòng)態(tài)數(shù)組
動(dòng)態(tài)數(shù)組附加 __strong
修飾符時(shí),使用方式和 C 語言的動(dòng)態(tài)數(shù)組類似讳窟。對(duì)于附加了 __weak
修飾符的動(dòng)態(tài)數(shù)組使用方式與 __strong
類似让歼。但是最好不要使用 __autoreleasing
去使用動(dòng)態(tài)數(shù)組(書中說,因?yàn)榕c設(shè)想的使用方法有差異丽啡,所以最好不用)谋右,但經(jīng)過測(cè)試它會(huì)對(duì)數(shù)組的每個(gè)元素都使用 __autoreleasing
(這樣的話,__autoreleasing
應(yīng)該最方便补箍,也應(yīng)該被推薦才對(duì)改执,因?yàn)槊恳粋€(gè)申請(qǐng)的對(duì)象系統(tǒng)都知道去釋放。* 這是個(gè)問題 *)坑雅,參見代碼 3-數(shù)組辈挂。 __unsafe_unretained
修飾符與 void *
一樣,就是 C 級(jí)別的指針裹粤,在 ARC 下不考慮终蒂。
NSObject * __strong *array = nil; // 不保證默認(rèn)初始化為 nil 哦
array = (NSObject * __strong *)calloc(entries, sizeof(NSObject *));
// calloc 函數(shù)申請(qǐng)內(nèi)存并將內(nèi)存初始化為 0
// 也可以使用 malloc 和 memset,申請(qǐng)內(nèi)存并初始化內(nèi)存為 0
// 但是不能使用:
// array = (id __strong *)malloc(sizeof(id) * entries);
// for (NSUInteger i = 0; i < entries; ++i) array[i] = nil;
// 因?yàn)?malloc 申請(qǐng)的內(nèi)存不保證值為 0,array[i] = nil 會(huì)調(diào)用 [array[i] release] 而出錯(cuò)
// 動(dòng)態(tài)數(shù)組和靜態(tài)數(shù)組一樣使用
array[0] = [[NSObject alloc] init];
for (NSUInteger i = 0; i < entries; ++i) array[i] = nil;
free(array);
// 釋放對(duì)象要使用 array[i] = nil后豫,相當(dāng)于 C++ 中調(diào)用對(duì)象的析構(gòu)函數(shù)悉尾。
// 不能像初始化一樣直接調(diào)用 memset,將數(shù)組置為 0挫酿,因?yàn)檫@樣并不會(huì)調(diào)用它的釋放函數(shù)。
// memcpy 和 realloc(可以減少內(nèi)存分配愕难,即釋放一部分內(nèi)存) 函數(shù)早龟,在使用的使用要小心,盡量不用猫缭。
補(bǔ)充
盡管該文章都在使用對(duì)象的 retainCount
去判斷對(duì)象的狀態(tài)葱弟,但是被銷毀的對(duì)象或給系統(tǒng)一個(gè)隨機(jī)地址,系統(tǒng)都會(huì)默認(rèn)返回 retainCount
為 1猜丹,所有不要把這個(gè)值在實(shí)際程序中作為判斷依據(jù)芝加。參考代碼 5-系統(tǒng)返回 retainCount
@autoreleasepool {
long t = 0; // 用 t 保存地址
{
NSObject * __strong a = [[NSObject alloc] init];
NSObject * __strong b = a;
t = (long)b;
NSLog(@"塊內(nèi): retainCount = %lu", CFGetRetainCount((void *)t)); // 輸出 2
}
// 如果上面塊內(nèi)的 __strong 沒有被釋放,那么這里也應(yīng)該輸出 2射窒;所以它們被釋放了藏杖,且對(duì)象被銷毀。
// 一個(gè)被銷毀的對(duì)象仍然會(huì)輸出 1
NSLog(@"塊外:retainCount = %ld", CFGetRetainCount((void *)t));
// 即使傳入 nil 也會(huì)輸出 1
NSLog(@"nil:retainCount = %lu", _objc_rootRetainCount(nil));
}