一篇文章徹底弄清ARC始末
自動(dòng)引用計(jì)數(shù)(ARC)是編譯器的一個(gè)特色,提供了Objective-C對象的自動(dòng)內(nèi)存管理機(jī)制。比起不得不考慮retain和release操作,ARC讓你更加專注于應(yīng)用中那些有趣的代碼,如對象圖,對象關(guān)系。
摘要(Summary)
通過在編譯期添加代碼的方式,ARC保證所有對象按需存在,按需釋放。從概念上來講,它與手動(dòng)引用計(jì)數(shù)(參見 Advanced Memory Management Programming Guide)有著相同的內(nèi)存管理約定,二者都會(huì)為你添加合適的內(nèi)存管理方法調(diào)用。 為了編譯器能生成正確的代碼,ARC限定了你可以使用的一些方法,以及toll-free橋接的使用方式(參見 “Toll-Free Bridged Types”).與此同時(shí)ARC還為對象引用(object references )和聲明式屬性(declared properties)引進(jìn)了新的生命周期限定符(lifetime qualifiers )。 ARC適用于OS X v10.6和v10.7(64位)下的Xcode4.2,以及IOS 4和IOS5.OS X v10.6和IOS4不支持弱引用。Xcode提供了自動(dòng)化的工具,完成ARC轉(zhuǎn)換過程中需要手工操作的部分(例如刪除retain和release方法調(diào)用),并且?guī)椭阍?遷移過程中不能自動(dòng)完成的操作(選擇Edit->Refactor->Convert to Objective-C ARC)。遷移工具將工程中的所有文件使用ARC進(jìn)行轉(zhuǎn)換。如果你覺得在某些文件中使用手動(dòng)引用計(jì)數(shù)會(huì)更方便,那么你也可以選擇在單獨(dú)文件中使用ARC。
參見:
Advanced Memory Management Programming Guide
Memory Management Programming Guide for Core Foundation
ARC概述(ARC Overview)
ARC會(huì)評估對象所需的生命期,并會(huì)在編譯期為你自動(dòng)插入合適的內(nèi)存管理調(diào)用方法,取代之前你不得不考慮何時(shí)需要使用 retain, release以及 autorelease的操作方式。編譯器也會(huì)為你生成合適的 dealloc方法??偟膩碚f,如果你僅使用ARC,傳統(tǒng)的Cocoa命名規(guī)范只會(huì)在你需要與使用手動(dòng)引用計(jì)數(shù)的代碼交互時(shí)才是重要的。一個(gè)完整正確的Person類的實(shí)現(xiàn)看起來可能是這樣的:
- @interface Person : NSObject
- @property NSString *firstName;
- @property NSString *lastName;
- @property NSNumber *yearOfBirth;
- @property Person *spouse;
- @end
- @implementation Person
- @end
(對象屬性(properties)默認(rèn)是強(qiáng)類型的( strong); strong屬性描述于 “ARC Introduces New Lifetime Qualifiers.”)
使用ARC,你可以如下實(shí)現(xiàn)一個(gè) contrived方法:
- - (void)contrived
- {
- Person *aPerson = [[Person alloc] init];
- [aPerson setFirstName:@"William"];
- [aPerson setLastName:@"Dudney"];
- [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];
- NSLog(@"aPerson: %@", aPerson);
- }
ARC會(huì)維護(hù)內(nèi)存管理,所以 Person和 NSNumber對象都不會(huì)泄露。
你也可以安全的實(shí)現(xiàn) Person類中的 takeLastnameFrom:方法。
- - (void)takeLastNameFrom:(Person *)person {
- NSString *oldLastname = [self lastName];
- [self setLastName:[person lastName]];
- NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
- }
ARC保證 oldLastName在 NSLog語句之前不會(huì)被釋放。
【ARC強(qiáng)制執(zhí)行新規(guī)則】(ARC Enforces New Rules)
為了正常運(yùn)轉(zhuǎn),ARC使用了一些在使用其它編譯模式下沒有的新規(guī)則。這些規(guī)則意在提供完全可靠的內(nèi)存管理模型;在某些情況下,它們僅使用***實(shí)踐方法,在其它情況下,它們僅簡化你的代碼或明顯的做出推論告知你不需要處理內(nèi)存管理。如果
你違反了這些規(guī)則,你會(huì)馬上得到一個(gè)編譯期錯(cuò)誤,而不是在運(yùn)行期可能會(huì)顯現(xiàn)的一個(gè)狡猾的bug。
1.不能顯示的調(diào)用dealloc,實(shí)現(xiàn)或調(diào)用 retain, release, retainCount,或 autorelease。
同樣也不要能使用 @selector(retain), @selector(release), 等等類似的選擇器。
如果你需要管理資源而不是釋放實(shí)例變量,那你可以實(shí)現(xiàn) dealloc方法。你不需要(事實(shí)上你不能)釋放實(shí)例變量,但你可能需要在系統(tǒng)類和其它的未使用ARC代碼中調(diào)用 [systemClassInstance setDelegate:nil] 方法。
在ARC中自定義的 dealloc方法不要調(diào)用 [super dealloc]方法(它實(shí)際上會(huì)導(dǎo)致編譯器錯(cuò)誤)。到super的鏈?zhǔn)秸{(diào)用是自動(dòng)的并且是編譯器強(qiáng)制執(zhí)行的。
你仍可以在Core Foundation樣式的的對象上,使用 CFRetain, CFRelease,和其它相關(guān)的函數(shù)。
2.你不能使用 NSAllocateObject 或 NSDeallocateObject
你使用 alloc來創(chuàng)建對象;運(yùn)行時(shí)系統(tǒng)會(huì)注意釋放這些對象。
3.你不能在C語言結(jié)構(gòu)體中使用對象指針。 與其使用一個(gè)結(jié)構(gòu)體( struct),不如創(chuàng)建一個(gè)Objective-C類來管理數(shù)據(jù)。
4. id與 void*之間不能隨意轉(zhuǎn)換
你必須使用特定的類型轉(zhuǎn)換來告訴編譯器對象的生命周期。你需要在Objective-C對象和以函數(shù)參數(shù)傳入的Core Foundation類型值之間進(jìn)行這樣的轉(zhuǎn)換。有關(guān)詳情,參見 “Managing Toll-Free Bridging”。
5.你不能使用 NSAutoreleasePool對象
ARC 提供了 @autoreleasepool來代替。這比 NSAutoreleasePool更高效。
6.你不能使用內(nèi)存區(qū)(memory zones)。
再也沒有使用 NSZone的必要了——現(xiàn)代的Obj-C運(yùn)行時(shí)會(huì)永遠(yuǎn)忽略它。
為了允許與自動(dòng)retain-release的代碼進(jìn)行交互,ARC在方法命名上加上了一個(gè)約束:
你不能以 new為開頭命名一個(gè)訪問器的名字。這反過來意味著你不必聲明一個(gè)以new開頭的屬性,除非你指定一個(gè)不同名稱的getter方法:
- // Won't work:
- @property NSString *newTitle;
- // Works:
- @property (getter=theNewTitle) NSString *newTitle;
【ARC引入新的生命周期限定符】(ARC Introduces New Lifetime Qualifiers)
ARC為對象引入了幾種新的生命周期限定符,以及弱類型引用( weak references)。弱類型應(yīng)用不會(huì)延長它所指向?qū)ο蟮纳芷?,一旦對象沒有強(qiáng)引用指向?qū)ο髸r(shí),弱引用會(huì)自動(dòng)變?yōu)? nil。
在程序中,你應(yīng)該充分利用這些限定符來管理對象圖。尤其是,ARC不能阻止強(qiáng)引用循環(huán)(strong reference cycles,之前稱為retain cycles,參見“Practical Memory Management”)的發(fā)生。審慎的使用弱類型關(guān)系會(huì)幫助你避免創(chuàng)建強(qiáng)引用循環(huán)。
【Property的屬性】(Property Attributes)
weak和 strong關(guān)鍵字是新引入的property聲明屬性,如下例所示:
- // 以下聲明等價(jià)于 @property(retain) MyClass *myObject;
- @property(strong) MyClass *myObject;
- // 以下聲明等價(jià)于"@property(assign) MyClass *myObject;"
- // 不同之處在于如果MyClass的實(shí)例變量釋放時(shí),其屬性值會(huì)設(shè)置為nil,而不會(huì)保存為一個(gè)野指針
- @property(weak) MyClass *myObject;
ARC下,對象類型的聲明默認(rèn)為 strong
【變量限定符】(Variable Qualifiers)
你可以向使用其它變量的限定符那樣,比如說, const,來使用以下生命周期限定符,
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
__strong是默認(rèn)的。只要有強(qiáng)類型指針指向一個(gè)對象,那么該對象會(huì)一直”生存“下去。
__weak表明一個(gè)不會(huì)維持所持對象生命期的引用。當(dāng)沒有強(qiáng)引用指向該對象時(shí),弱引用會(huì)設(shè)置為nil。
__unsafe_unretained指定一個(gè)引用,該引用不會(huì)維持所持對象的生命期,并且在沒有強(qiáng)引用指向?qū)ο髸r(shí)也不會(huì)設(shè)置為nil。如果它所指向的對象已經(jīng)被釋放,那么它會(huì)成為一個(gè)野指針。
__autoreleasing 用以指示以引用(id*)傳入的參數(shù)并在retun后自動(dòng)釋放。
你應(yīng)該正確的修飾變量。在對象變量的聲明時(shí)使用限定符的正確格式為:
- ClassName * qualifier variableName;
例如:
- MyClass * __weak myWeakReference;
- MyClass * __unsafe_unretained myUnsafeReference;
從技術(shù)上講其它的變體寫法都是錯(cuò)誤的,但編譯器都會(huì)”寬恕“它們。要弄清該問題,參見http://cdecl.org/.
在堆棧上使用 __weak變量時(shí)要當(dāng)心??紤]以下的例子:
- NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
- NSLog(@"string: %@", string);
雖然 string是在初始化賦值之后使用,但是在賦值的時(shí)候并沒有其它強(qiáng)引用指向字符串對象;因此字符串對象會(huì)馬上釋放掉。log語句顯示 stirng的值為 null。(編譯器對這種情況會(huì)提示警告)
你也要注意通過引用方式傳入的對象。以下代碼會(huì)正常運(yùn)轉(zhuǎn)
- NSError *error;
- BOOL OK = [myObject performOperationWithError:&error];
- if (!OK) {
- // Report the error.
- // ...
而error的聲明是隱式的:
- NSError * __strong e;
方法的聲明通常是:
- -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
因此編譯器會(huì)重寫代碼:
- NSError * __strong error;
- NSError * __autoreleasing tmp = error;
- BOOL OK = [myObject performOperationWithError:&tmp];
- error = tmp;
- if (!OK) {
- // Report the error.
- // ...
本地變量聲明( __strong)和參數(shù)( __autoreleasing)之間的區(qū)別導(dǎo)致編譯器創(chuàng)建臨時(shí)變量。在獲取__strong變量的地址時(shí)你可以通過將參數(shù)聲明為 id __storng*來獲得其原始指針?;蛘吣憧梢詫⒆兞柯暶鳛? __autoreleasing。
不要走開,下頁內(nèi)容更勁爆
#p#
【使用生命周期限定符來避免強(qiáng)類型循環(huán)引用】(Use Lifetime Qualifiers to Avoid Strong Reference Cycles)
你可以使用生命周期限定符來避免強(qiáng)類型循環(huán)引用。例如,通常如果你的對象圖形成于父-子層級結(jié)構(gòu)中,父對象需要引用它的子對象,反之亦然, 那么你構(gòu)造parent-to-child的關(guān)系為強(qiáng)類型,child-to-parent的關(guān)系為弱類型。其它情況可能會(huì)更加微妙,尤其是在涉及到 block對象時(shí)。
在手動(dòng)引用技術(shù)的模式下, __block id x,不會(huì)保留x。在ARC模式下, __block id x;默認(rèn)會(huì)保留x(就像其它值一樣)。為了在ARC下達(dá)到手動(dòng)引用技術(shù)的效果,你可以使用 __unsafe__unretained __block id x ;然而,就像 __unsafe_unretained名字蘊(yùn)含的那樣,擁有一個(gè)未保留變量(non-retained variable)是危險(xiǎn)的(因?yàn)樗赡苁且粋€(gè)野指針),因此***不要使用。有兩個(gè)更好的選擇是要么使用 __weak(如果你不需要支持IOS4或OS X v10.6),要門設(shè)置 __block值nil來打破循環(huán)引用。
以下的代碼片段說明了在手動(dòng)引用技術(shù)下時(shí)常會(huì)使用的模式。
- MyViewController *myController = [[MyViewController alloc] init…];
- // ...
- myController.completionHandler = ^(NSInteger result) {
- [myController dismissViewControllerAnimated:YES completion:nil];
- };
- [self presentViewController:myController animated:YES completion:^{
- [myController release];
- }];
如上所屬,你可以使用 __block限定符來替代,并在 completion處理方法中將myController的值設(shè)置為nil:
- MyViewController * __block myController = [[MyViewController alloc] init…];
- // ...
- myController.completionHandler = ^(NSInteger result) {
- [myController dismissViewControllerAnimated:YES completion:nil];
- myController = nil;
- };
或者,你可以使用一個(gè)臨時(shí)的 __weak變量。以下代碼列舉了一個(gè)簡單的實(shí)現(xiàn):
- MyViewController *myController = [[MyViewController alloc] init…];
- // ...
- MyViewController * __weak weakMyViewController = myController;
- myController.completionHandler = ^(NSInteger result) {
- [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
- };
而對于特殊的循環(huán),你應(yīng)該使用:
- MyViewController *myController = [[MyViewController alloc] init…];
- // ...
- MyViewController * __weak weakMyController = myController;
- myController.completionHandler = ^(NSInteger result) {
- MyViewController *strongMyController = weakMyController;
- if (strongMyController) {
- // ...
- [strongMyController dismissViewControllerAnimated:YES completion:nil];
- // ...
- }
- else {
- // Probably nothing...
- }
- };
某些情況下如果一個(gè)類不兼容__ weak,你可以使用 __unsafe_unretained。但這也不適用于特殊的循環(huán),因?yàn)樵趯?shí)際問題中驗(yàn)證 __unsafe_unretained指針是否仍有效或是否仍指向相同的對象,是非常困難或根本不可能實(shí)現(xiàn)的。
【ARC使用新聲明來管理自動(dòng)釋放池】(ARC Uses a New Statement to Manage Autorelease Pools)
使用ARC,你不能直接使用NSAutoreleasePool類來管理自動(dòng)釋放池。相反,你需要使用 @autoreleasepool塊:
- @autoreleasepool {
- // Code, such as a loop that creates a large number of temporary objects.
- }
這個(gè)簡單的結(jié)構(gòu)讓編譯器來判斷引用計(jì)數(shù)的狀態(tài)。在入口,自動(dòng)釋放池會(huì)被push。在正常退出( break,return,goto,fall-through,dengd)自動(dòng)釋放池會(huì)被pop。出于對已有代碼兼容性的考慮,如果因異常導(dǎo)致退出,自動(dòng)釋放池將不會(huì)被pop。
該語法在所有Objecti-C模式下都可用。相比使用 NSAutoreleasePool類來說, @autoreleasepool更加高效;因此鼓勵(lì)你在使用 NSAutoreleasePool的地方使用 @autoreleasepool。
【跨平臺(tái)下Outlets管理保持一致的模式】(Patterns for Managing Outlets Become Consistent Across Platforms)
ARC下的IOS和OS X中聲明outlets的模式已經(jīng)發(fā)生變化,并且在跨平臺(tái)下保持一致性。你通常應(yīng)該接受的是:outlets應(yīng)該是 weak的,除了那些在nib文件(或storyboard屏)中File‘s Owner指向的***對象應(yīng)該是 strong的。
Resource Programming Guide.中的 ”Nib Files“做出了全面的解釋。
【堆變量會(huì)初始化為nil】(Stack Variables Are Initialized with nil)
使用ARC,strong,weak,以及autoreleasing的堆變量現(xiàn)在會(huì)隱式的初始化為nil,例如:
- - (void)myMethod {
- NSString *name;
- NSLog(@"name: %@", name);
- }
輸出的 name的值為null而不可能是程序崩潰。
【使用編譯器指令來啟用或禁用ARC】(Use Compiler Flags to Enable and Disable ARC)
使用 -fobjc-arc編譯指令來啟動(dòng)ARC。如果在某些文件中使用手動(dòng)引用技術(shù)對你來說更方便些,你可以選擇在單獨(dú)的文件上使用ARC。對于默認(rèn)使用ARC的工程,你可以使用 fno-objc-arc編譯指令來禁用某個(gè)文件的ARC。
Xcode4.2及更高版本,OS X v10.6及更高版本(64為應(yīng)用),IOS4版本或更高版本支持ARC。OS X v10.6和IOS4不支持弱引用。Xcode4.1及早期版本的Xcode不支持ARC。
【管理Toll-Free橋接】(Managing Toll-Free Bridging)
在許多Cocoa應(yīng)用中,你可以使用Core Foundation樣式的對象,不管是來自于Core Foundation框架本身(例如, CFArrayRef 或 CFMutableDictionaryRef),還是來自于諸如Core Graphics一樣采用Core Foundation約定的框架(你可能使用向 CGColorSpaceRef and CGGradientRef樣式的類型)。
編譯器不會(huì)自動(dòng)管理Core Foundation對象的生命周期;你必須調(diào)用符合Core Founda內(nèi)存管理規(guī)則的CFRetain和CFRelease方法(或者合適的特殊類型變體)來釋放對象。(參見 Memory Management Programming Guide for Core Foundation)
如果你在Objective-C對象和Core Foundation樣式對象間執(zhí)行類型轉(zhuǎn)換,你要告訴編譯器對象的歸屬語義,不管對象是使用類型轉(zhuǎn)換(定義在objc/runtime.h)或Core Foundation樣式的宏(定義在NSObject.h)。
__bridge在Objective-C和Core Foundation間的指針轉(zhuǎn)換不附加對象的所有權(quán)。
__bridge_retained或 CFBridgingRetain將Objectiv-C指針轉(zhuǎn)換為Core Foundation指針,并且將對象的所有權(quán)轉(zhuǎn)給你。你負(fù)責(zé)調(diào)用CFRelease或相關(guān)的函數(shù)來放棄對象的所有權(quán)。
__bridge_transfer或 CFBridgingRelease將一個(gè)非Objective-C指針轉(zhuǎn)移到Objective-C指針,并將對象所有權(quán)轉(zhuǎn)交給ARC。ARC負(fù)責(zé)放棄對象的所有權(quán)。
例如,如果你有這樣的代碼:
- - (void)logFirstNameOfPerson:(ABRecordRef)person {
- NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
- NSLog(@"Person's first name: %@", name);
- [name release];
- }
你應(yīng)該替代為:
- - (void)logFirstNameOfPerson:(ABRecordRef)person {
- NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
- NSLog(@"Person's first name: %@", name);
- }
【編譯器處理Cocoa方法返回的CF對象】(The Compiler Handles CF Objects Returned From Cocoa Methods)
編譯器了解Objective-C的那些沿用Cocoa命名規(guī)則返回Core Foundation類型的方法(參見 Advanced Memory Management Programming Guide)。例如,編譯器知道,在IOS中, UIColor的 CGColor方法返回的CGColor不會(huì)被擁有。你仍需使用合適的類型轉(zhuǎn)換,如下例所示:
- NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
- [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
【使用所有權(quán)關(guān)鍵字對函數(shù)參數(shù)進(jìn)行強(qiáng)制類型轉(zhuǎn)換】(Cast Function Parameters Using Ownership Keywords)
函數(shù)調(diào)用中在Objective-C和Core Foundation對象之間進(jìn)行轉(zhuǎn)換,你要告訴編譯器被傳入的對象的所有權(quán)語義。Core Foundation的所有權(quán)規(guī)則在Core Foundation的內(nèi)存管理規(guī)則中都已近說明了(參見 Memory Management Programming Guide for Core Foundation);Objective-C對象的規(guī)則在 Advanced Memory Management Programming Guide.有說明。
在下面的代碼片段中,傳入 CGGradientCreateWithColors函數(shù)的數(shù)組需要一個(gè)合適的類型轉(zhuǎn)換。 arrayWithObjects:方法返回的對象的所有權(quán)不會(huì)傳給函數(shù),因此需要用 __bridge轉(zhuǎn)換。
- NSArray *colors = <#An array of colors#>;
- CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
以下方法實(shí)現(xiàn)中展示了代碼片段。要注意的是遵從Core Foundation內(nèi)存管理規(guī)則的Core Foundation內(nèi)存管理函數(shù)的用法。
- - (void)drawRect:(CGRect)rect {
- 02 CGContextRef ctx = UIGraphicsGetCurrentContext();
- 03 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
- 04 CGFloat locations[2] = {0.0, 1.0};
- 05 NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
- 06 [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
- 07 CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
- CGColorSpaceRelease(colorSpace); // Release owned Core Foundation object.
- CGPoint startPoint = CGPointMake(0.0, 0.0);
- CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
- CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,
- kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
- CGGradientRelease(gradient); // Release owned Core Foundation object.
- }
不要走開,下頁內(nèi)容更銷魂
#p#
【工程轉(zhuǎn)換時(shí)的常見問題】(Common Issues While Converting a Project)
當(dāng)遷移已有工程時(shí),可能你會(huì)遇到許多問題。這里列舉一些常見問題,以及解決辦法。
1.不能調(diào)用 retain, release,或 autorelease。
這是一個(gè)特殊點(diǎn)。你也不能寫:
while ([x retainCount]) { [x release]; }
2.不能調(diào)用 dealloc
如果你實(shí)現(xiàn)了一個(gè)單例對象或在init方法中替換一個(gè)對象,你通常會(huì)調(diào)用 dealloc方法。對于單例對象,使用共享實(shí)例模式。在 init方法中,你不再需要調(diào)用dealloc,因?yàn)閷ο笤谀阒貙? self時(shí)會(huì)被釋放掉。
3.不能使用 NSAutoreleasePool對象
使用新的 @autoreleasepool{}結(jié)構(gòu)代替。這在你的自動(dòng)釋放池中強(qiáng)制使用block結(jié)構(gòu),并且比 NSAutoreleasePool要快六倍。即使在非ARC的代碼中 @autoreleasepool也能工作,因?yàn)? @autoreleasepool較 NSAutoreleasePool要快得多,所以許多舊的”性能hacks“會(huì)無條件的被 @autoreleasepool所替代。
遷移器只處理 NSAutoreleasePool的簡單用法,不能處理復(fù)雜條件下的情況,或者一個(gè)定義在新的 @autoreleasepool結(jié)構(gòu)內(nèi)部的變量并在其之外使用的情況。
4.ACR需要你將在init方法中 [super init]的結(jié)果賦值給 self。
下面的代碼在ARC的init方法中是不合法的:
- [super init];
簡單的修正是做如下變換
- self = [super init];
更全面的改法是在繼續(xù)之前檢測結(jié)果是否為nil:
- self = [super init];
- if (self) {
- ...
5.不能實(shí)現(xiàn)自定義的 retain和 release方法
實(shí)現(xiàn)自定義的 retain和 release方法會(huì)打破弱引用。有幾種常見原因是想要提供提供自定義實(shí)現(xiàn)的:
性能
請不要再這么做了; NSObject的 retain和 release現(xiàn)在更加快捷。如果你仍發(fā)現(xiàn)問題,請?zhí)峤籦ug。
實(shí)現(xiàn)自定義的弱指針系統(tǒng)
使用 __weak代替
實(shí)現(xiàn)單例類
使用共享示例代替?;蚴褂妙惙椒愄娲鷮?shí)例方法,這避免了一直都要去分配對象空間的操作。
6."Assigned"實(shí)例變量成為了strong類型
在ARC之前,實(shí)例變量是非擁有引用(non-owning redernces)——直接將對象賦值給實(shí)例變量不會(huì)擴(kuò)展對象的生命周期。為了令屬性成為強(qiáng)類型,你通常只要實(shí)現(xiàn)或合成訪問器方法,它們會(huì)調(diào)用合適的內(nèi) 存管理方法;相比較而言,你可能已經(jīng)如下例子中實(shí)現(xiàn)了訪問器方法來維護(hù)一個(gè)弱類型屬性。
- @interface MyClass : Superclass {
- id thing; // Weak reference.
- }
- // ...
- @end
- @implementation MyClass
- - (id)thing {
- return thing;
- }
- - (void)setThing:(id)newThing {
- thing = newThing;
- }
- // ...
- @end
使用ARC,實(shí)例變量默認(rèn)是強(qiáng)類型引用——將實(shí)例變量直接賦值給對象的確會(huì)擴(kuò)展對象的生命周期。遷移工具沒辦法決定實(shí)例變量何時(shí)應(yīng)該是 weak類型的。為了像之前那樣維護(hù)同樣的行為,你必須將實(shí)例變量標(biāo)記為 weak類型,或使用一個(gè)聲明式屬性。
- @interface MyClass : Superclass {
- id __weak thing;
- }
- // ...
- @end
- @implementation MyClass
- - (id)thing {
- return thing;
- }
- - (void)setThing:(id)newThing {
- thing = newThing;
- }
- // ...
- @end
- 或
- @interface MyClass : Superclass
- @property (weak) id thing;
- // ...
- @end
- @implementation MyClass
- @synthesize thing;
- // ...
- @end
7.在C語言結(jié)構(gòu)體重不能使用強(qiáng)類型的ids,例如,以下代碼會(huì)編譯不過
- struct X { id x; float y; };
因?yàn)閤默認(rèn)肯定是被保留的,編譯器無法安全的合成所有保證代碼正常運(yùn)轉(zhuǎn)所需的所有代碼。例如,如果你通過一些最終會(huì)被釋放的代碼來傳入一個(gè)指針到這些結(jié)構(gòu) 體后,每個(gè)id在結(jié)構(gòu)體回收之前會(huì)被被迫釋放掉。編譯器不能可靠的做到這點(diǎn),所以在結(jié)構(gòu)體中的強(qiáng)類型的ids在ARC模式下是完全無效的。以下有幾個(gè)可能 的解決辦法:
(1)使用Objective-C對象代替結(jié)構(gòu)體
這是***的實(shí)踐方法。
(2)如果使用Objective-C對象是次優(yōu)方案,(可能你想要這些結(jié)構(gòu)體組成的一個(gè)數(shù)組)那么考慮void *進(jìn)行替代。
這需要使用顯示的類型轉(zhuǎn)換,在下面會(huì)提到。
(3)將對象引用標(biāo)記為__unsafe_unretained
該方法對于一些不常見的模式可能會(huì)有效,如下:
- struct x { NSString *S; int X; } StaticArray[] = {
- @"foo", 42,
- @"bar, 97,
- ...
- };
這可能存在問題,并且如果對象在指針之外會(huì)被釋放掉,這是不安全的,但實(shí)際對于像字符串常量一樣一直存在的東西來說確實(shí)很有用的。
8.不能在 id與 void *(包括Core Foundation類型)之間進(jìn)行直接轉(zhuǎn)換
在 “Managing Toll-Free Bridging.”中由詳細(xì)的描述。
【常見問題】(Frequently Asked Questions)
我該怎樣理解ARC?它把retians/releases放在哪里呢?
嘗試不要再考慮retain/release應(yīng)該放在哪,要考慮你應(yīng)用的邏輯??紤]你對象中的”strong和weak“指針,對象的所有權(quán),可能的循環(huán)引用。
我是否還需要為我的對象編寫dealloc方法?
可能需要。
因?yàn)锳RC不會(huì)自動(dòng)操作malloc/free,Core Foundation對象的生命周期管理,文件描述,等等。你仍需要在dealloc方法中是否這樣的資源。
你不需要(實(shí)際根本不需要)釋放實(shí)例變量,但是你需要在系統(tǒng)類和其它未使用ARC編譯的代碼上調(diào)用[self setDelegate:nil]。
ARC中的dealloc方法不需要或不允許調(diào)用[super dealloc];到super的鏈?zhǔn)浇Y(jié)構(gòu)是在運(yùn)行期進(jìn)行處理和實(shí)施的。
在ARC中仍存在循環(huán)引用嗎?
是的。
ARC自動(dòng)處理retain/release,并且繼承了循環(huán)引用的問題。幸運(yùn)的是,遷移到ARC的代碼很少內(nèi)存泄露,因?yàn)閷傩裕╬roperty)已經(jīng)聲明了是否要保留。
在ARC下我能創(chuàng)建C數(shù)組的保留指針不?
是的,可以!
- // Note calloc() to get zero-filled memory.
- __strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(sizeof(SomeClass *), entries);
- for (int i = 0; i < entries; i++) {
- dynamicArray[i] = [[SomeClass alloc] init];
- }
- // When you're done, set each entry to nil to tell ARC to release the object.
- for (int i = 0; i < entries; i++) {
- dynamicArray[i] = nil;
- }
- free(dynamicArray);
有些其它需要注意的地方:
某些情況下你需要使用__strong SomeClass **,因?yàn)槟J(rèn)是__autoreleasing SomeClass **.
分配的內(nèi)存必須是零填充的
你必須在釋放數(shù)組(memset或bzero無法工作)之前將每個(gè)元素設(shè)置為nil.
避免使用memcpy或realloc
ARC運(yùn)行速度會(huì)慢嗎?
這取決于你的測量方式,但基本”很快“。編譯器高效的消除許多外部的 retian和 release調(diào)用,而且總的來說會(huì)更加關(guān)注于提升Objective-C運(yùn)行時(shí)的速度。尤其是,當(dāng)調(diào)用者是ARC代碼時(shí),常用的"返回一個(gè) retain/autoreleased對象"模式會(huì)更快速而且實(shí)際上并沒有將對象放入到自動(dòng)釋放池中。
我可以在特定文件下退出ARC嗎?
可以。
在你遷移工程來使用ARC時(shí),-fobjc-arc編譯器標(biāo)記默認(rèn)會(huì)設(shè)置到每個(gè)Objective-C的源文件。你可以使用 -fno-objc-arc編譯器標(biāo)記來在特定的類中禁用ARC。在Xcode中,在target的 Build Phases中,打開 Compile Sources 組顯示源文件列表。雙擊你想要設(shè)置標(biāo)記的文件,在pop-up面板中輸入-fno-objc-arc指定,之后點(diǎn)擊完成。