自動(dòng)引用計(jì)數(shù)

自動(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ì)象自己持有个初,是指使用以 allocnew猴蹂、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
對(duì)函數(shù)返回值進(jìn)行優(yōu)化.jpg

引用計(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

__strongid 類型和對(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));
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脉顿,一起剝皮案震驚了整個(gè)濱河市蝌麸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艾疟,老刑警劉巖来吩,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蔽莱,居然都是意外死亡弟疆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門盗冷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怠苔,“玉大人,你說我怎么就攤上這事正塌∴致裕” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵乓诽,是天一觀的道長帜羊。 經(jīng)常有香客問我,道長鸠天,這世上最難降的妖魔是什么讼育? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上奶段,老公的妹妹穿的比我還像新娘饥瓷。我一直安慰自己,他們只是感情好痹籍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布呢铆。 她就那樣靜靜地躺著,像睡著了一般蹲缠。 火紅的嫁衣襯著肌膚如雪棺克。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天线定,我揣著相機(jī)與錄音娜谊,去河邊找鬼。 笑死斤讥,一個(gè)胖子當(dāng)著我的面吹牛纱皆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芭商,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼派草,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蓉坎?” 一聲冷哼從身側(cè)響起澳眷,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛉艾,沒想到半個(gè)月后钳踊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勿侯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年拓瞪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片助琐。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祭埂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兵钮,到底是詐尸還是另有隱情蛆橡,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布掘譬,位于F島的核電站泰演,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏葱轩。R本人自食惡果不足惜睦焕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一藐握、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垃喊,春花似錦猾普、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耕突,卻和暖如春笤成,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背眷茁。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纵诞,地道東北人上祈。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像浙芙,于是被迫代替她去往敵國和親登刺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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