《OC高級(jí)編程》之自動(dòng)引用計(jì)數(shù)(二)

ARC規(guī)則

?

所有權(quán)修飾符

? ? ARC有效時(shí),id 類(lèi)型和對(duì)象類(lèi)型同 C 語(yǔ)言其他類(lèi)型不同,類(lèi)型上必須附加所有權(quán)修飾符:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing
  1. __strong 是 id 和對(duì)象類(lèi)型默認(rèn)的所有權(quán)修飾符,沒(méi)明確規(guī)定時(shí)默認(rèn)為此
{
    id __strong obj = [[NSObject alloc] init];
}

//ARC無(wú)效
{
    id obj = [[NSObject alloc] init];
    [obj release];
}

//以上兩個(gè)代碼一樣
//持有強(qiáng)引用的對(duì)象在超出其作用域時(shí)被廢棄,引用的對(duì)象隨之釋放

? ?取得非自己生成并持有的對(duì)象

id __strong obj = [NSMutableArray array];
//強(qiáng)引用所以持有

? ? __strong 修飾的變量,不僅在變量作用域,在賦值上也能夠正確管理對(duì)象所有者

? ? 在類(lèi)成員變量中,也可以在方法參數(shù)上使用附有 __strong 修飾符的變量

? ? __ strong,__ weak, __autoreleasing 可以保證將附有這些修飾符的自動(dòng)變量初始化為 nil

  1. __weak 是為了解決循環(huán)引用引起的內(nèi)存泄露,內(nèi)存泄漏是應(yīng)當(dāng)廢棄的對(duì)象在超出生存周期后繼續(xù)存在(對(duì)自身的強(qiáng)引用也會(huì)發(fā)生循環(huán)引用
id __weak obj = [[NSObject alloc] init];
//警告,生產(chǎn)對(duì)象會(huì)被立即釋放
id __strong obj0 = [[NSObject alloc] init];
id __weak obj1 = obj0
//可以

? ? 在持有某對(duì)象弱引用時(shí),若該對(duì)象被廢棄,則弱引用自動(dòng)失效并處于 nil

  1. __ unsafe_unretained 是不安全的所有權(quán)修飾符,修飾的變量不屬于編譯器的內(nèi)存管理對(duì)象

? ? 同 __weak 一樣,無(wú)法持有生成對(duì)象,但持有對(duì)象廢棄時(shí)不會(huì)歸為 nil,再訪問(wèn)很可能崩潰,在 iOS4 及 OS X Snow Leopardde 應(yīng)用程序中,必須用 unsafe 代替 weak,在通過(guò) unsafe 修飾的變量使用其對(duì)象時(shí),必須確保其確實(shí)存在

  1. __autoreleasing 當(dāng) ARC 有效時(shí)不能使用 autorelease 方法,也不能使用NSAutoreleasePool 類(lèi)
@autoreleasepool{
    id __autoreleasing obj = [[NSObject alloc] init];
}

? ? 用 @autoreleasepool 塊來(lái)替代 NSAutoreleasePool 類(lèi)對(duì)象生產(chǎn)持有及廢棄

? 非顯示使用 __autoreleasing 的情況:

  • 取得非自己生成并持有的對(duì)象時(shí),被注冊(cè)到 autoreleasepool ,編譯器會(huì)檢查方法名是否以 alloc/new/copy/mutableCopy 開(kāi)始,如果不是,對(duì)象作為函數(shù)的返回值將自動(dòng)注冊(cè)到 autoreleasepool

  • 訪問(wèn) __weak 變量時(shí),必定要訪問(wèn)注冊(cè)到 autoreleasepool 的對(duì)象

    id __weak obj1 = obj0;
    NSLog(@"class=%@", [obj1 class]);
    //等同于
    id __weak obj1 = obj0;
    id __autoreleasing tmp = obj1;
    NSLog(@"class=%@", [tmp class]);
    

? ? 在訪問(wèn) __weak 變量引用的對(duì)象時(shí),對(duì)象可能被廢棄,把訪問(wèn)對(duì)象注冊(cè)到autoreleasepool,那么在 @autoreleasepool 塊結(jié)束前都能確保存在

  • id 指針 id *obj 和對(duì)象指針 NSObject **obj 在沒(méi)有顯示指定時(shí)會(huì)附加 __autoreleasing

    等同于 id _autoreleasing *obj 和 NSObject * _autoreleasing *obj

    NSError *error = nil;
    NSError **pError = &error;
    //編譯錯(cuò)誤
    //賦值給對(duì)象指針時(shí),所有權(quán)修飾符必須一致
    NSError *error = nil;
    NSError *__strong *pError = &error; 
    //編譯正常
    

?

規(guī)則

  • 不能使用 retain/release/retainCount/autorelease

    內(nèi)存管理是編譯器的工作

  • 不能使用 NSAllocateObject/NSDeallocateObject

  • 需遵循內(nèi)存管理的方法命名規(guī)則

? ? alloc/new/copy/mutableCopy 開(kāi)始命名的方法返回對(duì)象時(shí),必須返回給調(diào)用方所應(yīng)當(dāng)持有的對(duì)象

? ? init 開(kāi)始的方法必須是實(shí)例方法,必須返回對(duì)象,應(yīng)為 id 類(lèi)型或方法聲明類(lèi)的對(duì)象類(lèi)型,或該類(lèi)的超類(lèi)或子類(lèi),不注冊(cè)到 autoreleasepool 上,只是對(duì) alloc 方法返回值的對(duì)象進(jìn)行初始化處理并返回。

-(void) initialize 不包含在上述規(guī)則里

  • 不要顯式調(diào)用 delloc

? ? 不管 ARC 是否有效,對(duì)象廢棄時(shí)都會(huì)調(diào)用 delloc,有效時(shí)不必寫(xiě) [super delloc]

  • 使用 @autoreleasepool 代替

  • 不能使用 NSZone

? ? 不管 ARC 是否有效,區(qū)域在現(xiàn)在的運(yùn)行時(shí)系統(tǒng)都已被忽略

  • 對(duì)象型變量不能作為 C 語(yǔ)言結(jié)構(gòu)體的成員

? ? 因?yàn)?ARC 把內(nèi)存管理的工作分配給編譯器,所以編譯器必須能知道并管理對(duì)象的生存周期,但 C 語(yǔ)言無(wú)法管理結(jié)構(gòu)體成員的生存周期。要把對(duì)象加入結(jié)構(gòu)體成員中,可強(qiáng)制轉(zhuǎn)換為 void* 或附加 __unsafe_unretained (注意避免內(nèi)存泄露)

  • 顯示轉(zhuǎn)換 id 和 void*
/* ARC 無(wú)效 */
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release];
//id 和 void* 相互轉(zhuǎn)換沒(méi)問(wèn)題,但 ARC 有效時(shí)編譯錯(cuò)誤

/* ARC 有效 */
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
//id 或?qū)ο笮唾x給 void* 或者逆向賦值,都需要進(jìn)行特定轉(zhuǎn)換,如果只想單純賦值可以用"__bridge 轉(zhuǎn)換"

? ? 但轉(zhuǎn)換為 void* 的 __ bridge 轉(zhuǎn)換,安全性和 ___unsafe_unretained 相近,甚至更低

? ? 另兩種 __ bridge 轉(zhuǎn)換," __ bridge_retained 轉(zhuǎn)換"和" __ bridge_transfer 轉(zhuǎn)換"

//__bridge_retained 轉(zhuǎn)換
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
//使轉(zhuǎn)換賦值的變量也持有所賦值的對(duì)象\

/* ARC 無(wú)效 */
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

//__ bridge_transfer轉(zhuǎn)換
id obj = (__bridge_transfer id)p;
//與 retained 相反,被轉(zhuǎn)換的變量在持有對(duì)象被賦值給轉(zhuǎn)換變量后隨之釋放

/* ARC 無(wú)效 */
id obj = (id)p;
[obj retain];
[(id)p release];

? ? 如果使用以上兩種轉(zhuǎn)換,那么不使用 id 型或?qū)ο笮妥兞恳部梢陨沙钟屑搬尫艑?duì)象,雖然可以,但不推薦

? ? 這些轉(zhuǎn)換多用在 Object-C 對(duì)象和 Core Foundation 對(duì)象之間

Core Foundation 對(duì)象和 Object-C 對(duì)象區(qū)別很小,只是由哪個(gè)框架(Core Foundation 框架還是 Foundation 框架)生成。無(wú)論是哪種框架生成的對(duì)象,一旦生成,就能在不同框架中使用。兩者的轉(zhuǎn)換不需要額外的 CPU 資源,因此稱(chēng)為“免費(fèi)橋”(Toll-Free Bridge)

? ? 可以使用以下函數(shù)轉(zhuǎn)換 Object-C 對(duì)象和 Core Foundation 對(duì)象

CFTypeRef CFBridgingRetain(id X){
    return (__bridge_retained CFTypeRef)X;
}
id CFBridgingRelease(CFTypeRef X){
    return (__bridge_transfer id)X;
}
/* Object-C 對(duì)象轉(zhuǎn)為 Core Foundation 對(duì)象 */
CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init];
    cfObject = CFBridgingRetain(obj);
    CFShow(cfObject);
    printf("retain count = %d\n",CFGetRetainCount(cfObject));
    //=2
}
printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject)); //=1
CFRelease(cfObject);
//也可以用 __ bridge_retained 轉(zhuǎn)換替代 CFBridgingRetain
//用 __bridge 轉(zhuǎn)換替代會(huì)造成懸垂指針
/*Core Foundation 對(duì)象轉(zhuǎn)為 Object-C 對(duì)象 */
{
    CFMutableArrayRef cfObject =           CFArrayCreateMutable(kCFAlloctorDefault, 0, NULL);
    prinft("retain count = %d\n",CFGetRetainCount(cfObject));
    //=1
    id obj = CFbridgingRelease(cfObject);
    printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject)); //=1
    NSLog(@"class=%@",obj); 
}
//可用 __bridge_transfer 轉(zhuǎn)換替代 CFbridgingRelease
//用 __bridge 轉(zhuǎn)換替代會(huì)造成內(nèi)存泄漏

?

屬性

? ? ARC 有效時(shí),Object-C 類(lèi)的屬性也會(huì)發(fā)生變化

@property (nonatomic, strong) NSString *name;
屬性聲明的屬性 所有權(quán)修飾符
assign __unsafe_unretained
copy __strong(賦值的是被復(fù)制的對(duì)象)
retain __strong
strong __strong
unsafe_unretained __unsafe_unretained
weak __weak

? ? 以上各屬性賦值給指定的屬性中就相當(dāng)于賦值給附加各屬性對(duì)應(yīng)的所有權(quán)修飾符的變量中。只有 copy 不是簡(jiǎn)單的復(fù)制,是通過(guò) NSCopying 接口的copyWithZone: 方法復(fù)制賦值源所生成的對(duì)象

? ? 在聲明類(lèi)成員變量時(shí),同屬性聲明中的屬性不一致會(huì)引起編譯錯(cuò)誤
?

數(shù)組

//附有 __strong 修飾符的變量作為靜態(tài)數(shù)組使用
id objs[10];
//__ weak, __ autoreleasing, __ unsafe_unretained 也相同

? ? 除 __ unsafe_unretained 以外的修飾符保證其指定的變量初始化為 nil

? ? 數(shù)組變量超出作用域時(shí),數(shù)組中各個(gè)賦有 __strong 修飾符的變量也隨之失效,對(duì)象也隨之釋放

? ? 動(dòng)態(tài)數(shù)組時(shí),會(huì)選擇使用 NSMutableArray,NSMutableDictionary,NSMutableSet 等 Foundation 框架的容器,這些容器會(huì)恰當(dāng)?shù)爻钟凶芳拥膶?duì)象并管理

? ? 但也可以使用賦有 __strong 修飾符的變量聲明動(dòng)態(tài)數(shù)組,需要遵守一些事項(xiàng)

? ? 聲明動(dòng)態(tài)數(shù)組用指針

id __strong *array = nil;

? ? 因?yàn)椤?id *類(lèi)型” 默認(rèn)為“ id __ autoreleasing *類(lèi)型”,所以必須顯式指定為strong,以及雖然保證了賦有 __ strong 的 id 型變量被初始化為 nil,但不保證賦有__strong 的 id 指針型變量初始化為 nil

? ? 使用類(lèi)名時(shí)如下記述

NSObject * __strong *array = nil;

? ? 使用 calloc 函數(shù)確保想要分配的附有 __strong 修飾符變量的容量占有的內(nèi)存塊

//初始化
array = (id __strong *)calloc(entries, sizeof(id));

? ? 該代碼分配了 entries 格所需內(nèi)存塊,由于使用賦有 __strong 的變量前必須初始化為 nil,所以這里使用使分配區(qū)域初始化為 0 的 calloc 函數(shù)來(lái)分配內(nèi)存,也可以用 malloc 后用 memset 等將其填充為 0

//這樣很危險(xiǎn)
array = (id __strong *)malloc(entries, sizeof(id));
for(NSUInteger i = 0; i < entries; i++)
    array[i] = nil;

? ? 這樣內(nèi)存區(qū)域沒(méi)有初始化為 0,nil 會(huì)被賦值給 __strong 并被賦值了隨機(jī)地址的變量中,從而釋放一個(gè)不存在的變量,因此推薦使用 calloc

? ? 通過(guò) calloc 函數(shù)分配的動(dòng)態(tài)數(shù)組能完全像靜態(tài)數(shù)組一樣使用

array[0] = [[NSObject alloc] init];

? ? 但動(dòng)態(tài)數(shù)組中操作賦有 __strong 的變量,需要自己釋放所有元素

? ? 在只是簡(jiǎn)單地用 free 函數(shù)(即 free(array);)廢棄數(shù)組用內(nèi)存塊的情況下,數(shù)組各元素所賦值的對(duì)象不能再次釋放,從而引起內(nèi)存泄露

? ? 因?yàn)樵陟o態(tài)數(shù)組中,編譯器能夠根據(jù)變量作用域自動(dòng)插入釋放賦值對(duì)象的代碼,而在動(dòng)態(tài)數(shù)組中,編譯器不能確定數(shù)組的生存周期,無(wú)從處理 。一定要將 nil 賦值給所有元素,使得其所賦值對(duì)象的強(qiáng)引用失效,從而釋放對(duì)象,之后再 free

//釋放
for(NSUInteger i = 0; i < entries; i++)
    array[i] = nil;
free(array);

? ? 同初始化的注意事項(xiàng)相反,即使用 memset 將內(nèi)存填充為 0 也不會(huì)釋放所賦值的對(duì)象,會(huì)引起內(nèi)存泄露

? ? 同樣禁止使用 memcpy 函數(shù)拷貝數(shù)組元素以及 realloc 函數(shù)重新分配內(nèi)存塊

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容