1 什么是自動(dòng)引用計(jì)數(shù)
自動(dòng)引用計(jì)數(shù)是指內(nèi)存管理中對(duì)引用采取自動(dòng)計(jì)數(shù)的技術(shù)拴鸵。在 Objective-C 中采用 ARC 機(jī)制咒彤,讓編譯器來(lái)進(jìn)行內(nèi)存管理苗分。
2 內(nèi)存管理 / 引用計(jì)數(shù)
用開(kāi)關(guān)房間的燈為例來(lái)說(shuō)明引用計(jì)數(shù)的機(jī)制
2.1 內(nèi)存管理的思考方式
自己生成的對(duì)象正驻,自己所持有弊攘。
非自己生成的對(duì)象抢腐,自己也能持有。
不再需要自己持有的對(duì)象時(shí)釋放襟交。
非自己持有的對(duì)象無(wú)法釋放
自己生成的對(duì)象自己持有:alloc? new? ?copy? mutableCopy? 以及以這些名稱開(kāi)頭的方法名( allocMyObject? newThatObject? copyThis…)? ;? 不屬于這類的方法名(allocate? newer? coping…)
非自己生成的對(duì)象迈倍,自己也能持有:retain 方法
例: id obj = [NSMutableArray? array]; //取得對(duì)象存在,但自己不持有對(duì)象
[obj? retain]; //自己持有對(duì)象
不再需要自己持有的對(duì)象時(shí)釋放:release 方法
用 alloc/new/copy/mutableCopy 方法生成并持有的對(duì)象捣域,或者用 retain 方法持有的對(duì)象啼染,一旦不再需要,務(wù)必要用 release 方法進(jìn)行釋放竟宋。
release與autorelease? 的區(qū)別:release 立即釋放; autorelease 放入釋放池中提完,釋放池結(jié)束時(shí)釋放。
無(wú)法釋放非自己持有的對(duì)象:釋放非自己所持有的對(duì)象時(shí)會(huì)造成崩潰丘侠。
2.2 alloc/retain/release/dealloc 蘋(píng)果的實(shí)現(xiàn)? (使用引用計(jì)數(shù)表)
引用計(jì)數(shù)表各記錄中存有內(nèi)存塊地址徒欣,可從各個(gè)記錄追溯到各對(duì)象的內(nèi)存塊。
利用工具檢測(cè)內(nèi)存泄漏時(shí)蜗字,引用計(jì)數(shù)表的各記錄有助于檢測(cè)各對(duì)象的持有者是否存在
2.3 autorelease
autorelease:當(dāng)超出其作用域時(shí)打肝,對(duì)象實(shí)例的 release 實(shí)例方法被調(diào)用。
autorelease具體使用方法:
生成并持有 NSAutoreleasePool 對(duì)象挪捕。
調(diào)用已分配對(duì)象的 autorelease 實(shí)例方法粗梭。// [obj? autorelease]
廢棄 NSAutoreleasePool 對(duì)象。 // [pool drain]?
Cocoa 框架中有很多類方法用于返回 autorelease 的對(duì)象级零。比如 NSMutableArray 類的 arrayWithCapacity 類方法断医。
2.4 autorelease 蘋(píng)果的實(shí)現(xiàn)
NSObject 類的 autorelese 實(shí)例方法,將該對(duì)象追加到正在使用的 NSAutoreleasePool 對(duì)象的數(shù)組中奏纪。
查看已被 autorelease 的對(duì)象的狀態(tài):兩種
[NSAutoreleasePool? showPools]; //只能在 iOS 中使用
_objc_autoreleasePoolPrint( ); //在運(yùn)行時(shí)系統(tǒng)中調(diào)用
如果 autorelease NSAutoreleasePool 對(duì)象會(huì)如何鉴嗤?
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[pool autorelease];
答:會(huì)報(bào) Cannot autorelease an autorelease pool
調(diào)用對(duì)象的 autorelease 實(shí)例方法,實(shí)際上是調(diào)用 NSObject 類的 autorelease 實(shí)例方法序调。但是對(duì)于 NSAutoreleasePool 類醉锅,autorelease 實(shí)例方法已被該類重載,因此運(yùn)行時(shí)會(huì)出錯(cuò)发绢。
3 ARC 規(guī)則
3.1 概要
文件的編譯屬性設(shè)置:-fobjc-arc 或 -fno-objc-arc
3.2 內(nèi)存管理的思考方式
引用計(jì)數(shù)式內(nèi)存管理的思考方式就是思考 ARC 所引起的變化硬耍。
3.3 所有權(quán)修飾符
__strong 修飾符?
是 id 類型和對(duì)象類型默認(rèn)的所有權(quán)修飾符。
__strong边酒、__weak经柴、__autoreleasing 修飾,可以保證有這些修飾符的自動(dòng)變量初始化為nil墩朦。
通過(guò) __strong 修飾符口锭,不必再次鍵入 retain 或者 release,完美地滿足了“引用計(jì)數(shù)式內(nèi)存管理的思考方式”介杆。
__weak 修飾符
主要用于解決“循環(huán)引用”的問(wèn)題鹃操,用 __weak 修飾符可以避免循環(huán)引用。
id __weak obj = [[NSObject alloc] init];? //生成的對(duì)象會(huì)立即被釋放春哨。
__unsafe_unretained 修飾符
是不安全的所有權(quán)修飾符荆隘,__unsafe_unretained 修飾符的變量不屬于編譯器的內(nèi)存管理對(duì)象
賦值給附有 __unsafe_unretained 修飾符變量的對(duì)象在通過(guò)該變量使用時(shí),如果沒(méi)有確定其確實(shí)存在赴背,那么應(yīng)用程序就會(huì)崩潰椰拒。
為什么需要使用附有 __unsafe_unretained 修飾符的變量?
比如在 iOS4 以及 OS X Snow Leopard 的應(yīng)用程序中凰荚,必須使用 __unsafe_unretained 修飾符來(lái)替代 __weak 修飾符燃观。
__autoreleasing 修飾符
在 ARC 有效時(shí),用 @autoreleasepool 塊替代 NSAutoreleasePool 類便瑟,用附有 __autoreleasing 修飾符的變量替代 autorelease 方法缆毁。
不是以 alloc/new/copy/mutableCopy 開(kāi)頭的方法(init 系列方法除外) 返回的對(duì)象將自動(dòng)注冊(cè)到 autoreleasepool 。
在訪問(wèn)附有 __weak 修飾符的變量時(shí)到涂,實(shí)際上必定要訪問(wèn)注冊(cè)到 autoreleasepool 的對(duì)象脊框。?
原因:因?yàn)?__weak 修飾符只持有對(duì)象的弱引用,而在訪問(wèn)引用對(duì)象的過(guò)程中践啄,該對(duì)象有可能被廢棄浇雹。如果把要訪問(wèn)的對(duì)象注冊(cè)到 autoreleasepool 中,那么在 @autoreleasepool 塊結(jié)束之前都能確保該對(duì)象存在屿讽。因此昭灵,在使用附有 __weak 修飾符的變量時(shí)就必定要使用注冊(cè)到 autorelease 中的對(duì)象。?
id 的指針或?qū)ο蟮闹羔樤跊](méi)有顯式指定時(shí)會(huì)被附加上 __autorelease 修飾符伐谈。
賦值給對(duì)象指針時(shí)烂完,所有權(quán)修飾符必須一致。
NSError *error = nil;
NSError *__strong *pError = &error;
NSError __weak *error = nil;
NSError * __weak *pError = &error;
例:
方法聲明:
(BOOL) performOperationWithError: (NSError * __autoreleasing *)error;?
方法調(diào)用:
NSError __strong *error = nil;
BOOL return = [obj performOperationWithError: &error];
編譯器轉(zhuǎn)換為:
NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError: &tmp];
error = tmp;
方法聲明也可以寫(xiě)為:
(BOOL) performOperationWithError: (NSError * __strong *)error;?
即對(duì)象不注冊(cè)到 autoreleasepool 也能夠傳遞衩婚,但是為了貫徹內(nèi)存管理的思考方式窜护,我們要將參數(shù)聲明為附有 __autoreleasing 修飾符的對(duì)象指針類型。
3.4 規(guī)則
不能使用 retain/release/retainCount/autorelease
不能使用 NSAllocateObject/NSDeallocateObject
須遵守內(nèi)存管理的方法命名規(guī)則
用于對(duì)象生成/持有的方法必須遵守以下命名規(guī)則:以 alloc/new/copy/mutableCopy 名稱開(kāi)始的方法在返回對(duì)象時(shí)非春,必須返回給調(diào)用方所應(yīng)當(dāng)持有的對(duì)象柱徙。(alloc 是以 NSAllocateObject 分配對(duì)象)。
以 init 開(kāi)始的方法返回的對(duì)象不注冊(cè)到 autoreleasepool 上奇昙, 基本上只是對(duì) alloc 方法返回值的對(duì)象進(jìn)行初始化處理并返回該對(duì)象护侮。?
不要顯式調(diào)用 dealloc
使用 @autoreleasepool 塊替代 NSAutoreleasePool
不能使用區(qū)域 (NSZone)
不管 ARC 是否有效,區(qū)域在現(xiàn)在的運(yùn)行時(shí)系統(tǒng)中已單純地被忽略储耐。
對(duì)象型變量不能作為 C 語(yǔ)言結(jié)構(gòu)體 (struct/union) 的成員
原因:C 語(yǔ)言的規(guī)約上沒(méi)有方法來(lái)管理結(jié)構(gòu)體成員的生存周期羊初。因?yàn)?ARC 把內(nèi)存管理的工作分配給編譯器,所以編譯器必須能夠知道并管理對(duì)象的生存周期。
顯式轉(zhuǎn)換 “id” 和 ”void * “
通過(guò) “__bridge 轉(zhuǎn)換”长赞, id 和 void * 能夠相互轉(zhuǎn)換(__bridge轉(zhuǎn)換安全性低)
__bridge還有另外兩種轉(zhuǎn)換:__bridge_retained轉(zhuǎn)換? 和? __bridge_transfer轉(zhuǎn)換晦攒。
__bridge_retained轉(zhuǎn)換可使要轉(zhuǎn)換賦值的變量也持有所賦值的對(duì)象。
代碼:
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *) obj;
相當(dāng)于:ARC 無(wú)效時(shí)的源代碼
id obj = [[NSObject alloc] init];
void *p = obj;?
[(id)p retain];
__bridge_transfer轉(zhuǎn)換 被轉(zhuǎn)換的變量所持有的對(duì)象在該變量被賦值給轉(zhuǎn)換目標(biāo)變量后隨之釋放得哆。
代碼:
id obj = (__bridge_transfer id) obj;
相當(dāng)于:ARC 無(wú)效時(shí)的源代碼
id obj = (id) p;
[obj retain];
[(id)p release];
主要應(yīng)用于 Objective-C 對(duì)象與 Core Foundation 對(duì)象之間轉(zhuǎn)換脯颜。
3.5 屬性?
3.6 數(shù)組?
使用 calloc 函數(shù)確保想分配的附有 __strong 修飾符變量占有的內(nèi)存塊。
array = (id __strong *)calloc (entries,sizeof(id));
該源代碼分配了 entries 個(gè)所需的內(nèi)存塊贩据。由于使用附有 __strong 修飾符的變量前必須先將其初始化為 nil, 所以這里使用使分配區(qū)域初始化為0的 calloc 函數(shù)來(lái)分配內(nèi)存栋操。不使用 malloc 函數(shù),在用 malloc 函數(shù)分配內(nèi)存后可用 memset 等函數(shù)將內(nèi)存填充為0饱亮。
free(array)矾芙;
只是簡(jiǎn)單地用 free 函數(shù)廢棄了數(shù)組用內(nèi)存塊的情況下,數(shù)組各元素所賦值的對(duì)象不能再次釋放近上,從而引起內(nèi)存泄漏剔宪。
這是因?yàn)樵诒響B(tài)數(shù)組中,編譯器能夠根據(jù)變量的作用域自動(dòng)插入釋放賦值對(duì)象的代碼戈锻,而在動(dòng)態(tài)數(shù)組中歼跟,編譯器不能確定數(shù)組的生存周期,所以無(wú)從處理格遭。一定要將 nil 賦值給所有元素哈街,使得元素所賦值對(duì)象的強(qiáng)引用失效,從而釋放那些對(duì)象拒迅。在此之后骚秦,使用 free 函數(shù)廢棄內(nèi)存塊。
4 ARC 的實(shí)現(xiàn)
4.1 __strong 修飾符
objc_retainAutoreleasedReturnValue函數(shù)與 objc_autoreleaseReturnValue 函數(shù)
如果調(diào)用了 objc_autoreleaseReturnValue() 后緊接著調(diào)用 objc_retainAutoreleasedReturnValue() 璧微, 那么就不將返回的對(duì)象注冊(cè)到 autoreleasepool 中作箍,而是直接傳遞到調(diào)用方。達(dá)到最優(yōu)化前硫。?
4.2 __weak 修飾符
附有 __weak 修飾符的變量所引用的對(duì)象被廢棄胞得,則將 nil 賦值給該變量。
使用附有 __weak 修飾符的變量屹电,即是使用注冊(cè)到 autoreleasepool 中的對(duì)象阶剑。
對(duì)象被廢棄時(shí)最后調(diào)用的 objc_clear_deallocating 函數(shù)的動(dòng)作如下:
只在需要避免循環(huán)引用時(shí)使用__weak 修飾符
因?yàn)槭褂酶接?__weak 修飾符變量,會(huì)注冊(cè)到 autoreleasepool 的對(duì)象中危号。大量使用就會(huì)大量注冊(cè)牧愁,因此使用附有 __weak 修飾符的變量時(shí),最好先暫時(shí)賦值給附有 __strong 修飾符的變量后再使用外莲。
4.3 __autoreleasing 修飾符
將對(duì)象賦值給附有 __autoreleasing 修飾符的變量等同于 ARC 無(wú)效時(shí)調(diào)用對(duì)象的 autorelease 方法猪半。
4.4 引用計(jì)數(shù)
_objc_rootRetainCount函數(shù)可獲取指定對(duì)象的引用計(jì)數(shù)數(shù)值。
引用計(jì)數(shù)數(shù)值變化:
賦值給附有 __weak 修飾符的變量,引用計(jì)數(shù)不變磨确。
被注冊(cè)到 autoreleasepool 中的對(duì)象沽甥,引用計(jì)數(shù)會(huì) +1。
使用附有 __weak 修飾符的變量俐填,會(huì)將對(duì)象注冊(cè)到 autoreleasepool 中安接,引用計(jì)數(shù)會(huì) +1。
注:代碼使 __weak 生效