ARC實戰中的具體應用

ARC是一個編譯器特徵,它提供了對OC對象自動管理內存。ARC讓開發者專注於感興趣的代碼和對象的關係,而不用考慮對象的retain和release。

原文:Transitioning to ARC Release Notes(蘋果官方文檔)
 
ARC是一個編譯器特徵,它提供了對OC對象自動管理內存。ARC讓開發者專注於感興趣的代碼和對象的關係,而不用考慮對象的retain和release。
 
概要
ARC在編譯時期添加代碼,保證對象可用。概念上說,ARC遵循手動引用計數的規則,替開發者,在編譯時期添加合適的代碼。
 
Xcode4.2(Mac OS 10.6、10.7和iOS4和iOS5)支持ARC,弱引用在10.6和iOS4上不支持。
 
Xcode提供了一個工具:自動機械得轉化爲ARC(比如移除retain和release的調用),並幫助開發者解決不能自動遷移的問題。遷移工具將所有文件轉化成ARC,開發者也可以對單個文件實施ARC,方便於開發者對某些文件手動引用計數。
 
ARC概要
ARC評估了對象的生命週期,並自動插入合適的代碼在編譯時期,從而代替開發者不得不考慮何時需要retain、release和autolease。同編譯器替開發者,生成合適的dealloc方法。一般來說,當開發者需要在手動引用計數和ARC配合使用時候,使用傳統ARC的Cocoa命名慣例是很重要的。
 
一個正確的完整的Person類如下:
 
  1. @interface Person : NSObject   
  2. @property NSString *firstName;   
  3. @property NSString *lastName;   
  4. @property NSNumber *yearOfBirth;   
  5. @property Person *spouse;   
  6. @end   
  7.     
  8. @implementation Person   
  9. @end   
 
對象的屬性默認是strong;strong屬性在 “ARC Introduces New Lifetime Qualifiers.”有描述
 
  1. - (void)contrived {   
  2.     Person *aPerson = [[Person alloc] init];   
  3.     [aPerson setFirstName:@"William"];   
  4.     [aPerson setLastName:@"Dudney"];   
  5.     [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];   
  6.     NSLog(@"aPerson: %@", aPerson);   
  7. }   
 
ARC掌管了內存管理,所以Person和NSNumber不會泄露。
 
開發者還可以安全得實現Person類的方法takeLastNameFrom:
 
  1. - (void)takeLastNameFrom:(Person *)person {   
  2.     NSString *oldLastname = [self lastName];   
  3.     [self setLastName:[person lastName]];   
  4.     NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);   
  5. }   
 
ARC確保oldLastName在NSLog之前不會被銷燬。
 
ARC執行了新的規則
ARC執行了一些新的規則,是其他編譯模式沒有的。這個規則是:致力於提供一個安全可靠的內存管理模式。在某些情況下,他們只是執行最佳實踐;某些情況,他們簡化你的代碼或者處理那沒明顯的不需要內存問題。如果你違反這些規則,立即得到一個編譯錯誤,而不會在運行顯示一個微妙的bug。
 
● 開發者不能顯示調用dealloc;不能實現和調用retain、release、retainCount和autorelease。
禁止使用@selector(retain),@selector(release)等等。
開發者仍可以實現dealloc方法,如果你想管理資源而不是變量。開發者不能release變量,但是可以調用[systemClassInstance setDelegate:nil]在系統類上,或者其他不是用ARC編譯的代碼上。
ARC中自定義的dealloc方法,不需要調用[super dealloc](其實這樣做就會導致編譯錯誤),編譯器會強制自動鏈接到父類。
開發者仍可以對Core Foundation-style對象,使用CFRetain,CFRelease和其他相關方法。
 
● 不能使用NSAllocateObject或者NSDeallocateObject。開發者創建對象使用alloc,運行時環境負責銷燬對象。
 
● 在C數據結構中,不能使用對象指針。可以使用OC類來代替C的struct
 
● id和void*沒有自動轉換.
開發者必須使用特殊的類型轉化.開發者需要通過函數參數傳遞,在OC對象和Core Foundation之間做類型轉換。
 
● 開發者不能使用NSAutoreleasePool對象。ARC下使用@autoreleasepool,它比NSAtuoreleasePool更有效率。
 
● 開發者不能使用內存zones。不用再使用NSZone了。他們已經被現代的OC運行環境給忽略了。
 
爲了配合手動引用計數,ARC的方法命名有限制:
● 訪問器方法不能已new開頭,反過來就是:開發者不能聲明一個已new開頭的屬性,除非你給你指定一個getter
 
  1. // 不正確:   
  2. @property NSString *newTitle;   
  3.     
  4. // 正確:   
  5. @property (getter=theNewTitle) NSString *newTitle;   
 
ARC引入了新的Lifetime修飾符。
 
ARC引入幾個新的修飾符和弱(weak)引用。弱引用不會延伸到對象生命週期,並自動設置爲nil,當該對象沒有任何的強引用的時候。
 
開發者需要使用這些修飾來管理對象圖。通常ARC不對循環引用做警告。謹慎得使用弱引用可以保證不會循環引用。
 
Property屬性
weak和strong關鍵字作爲新的property屬性被引入,例如:
 
  1. // The following declaration is a synonym for: @property(retain) MyClass *myObject;   
  2. @property(strong) MyClass *myObject;   
  3.     
  4. // The following declaration is similar to "@property(assign) MyClass *myObject;"   
  5. // except that if the MyClass instance is deallocated,   
  6. // the property value is set to nil instead of remaining as a dangling pointer.   
  7. @property(weak) MyClass *myObject;   
 
ARC下,strong是默認property屬性
 
變量的修飾符
開發者使用下面得lifetime修飾。
__strong
__weak
__unsafe_unretained
__autoreleasing
 
● 默認是__strong。只要對象還有強引用,該對象“活着”。
● __weak不保留對象,只是簡單引用。weak對象將被設置nil,當對象沒有任何強引用的時候。
● __unsafe_unretained 不保留對象,只是簡單引用。但是不設置爲nil,當對象沒有任何強引用得時候。如果對象被銷燬,__unsafe_unretained的對象將會野指針。
● __autoreleasing 用於標識id*的引用參數,或者需要自動釋放的返回的對象。
 
開發者需要正確修飾變量。使用下面的格式來修飾變量聲明。
 
類名*  修飾  變量名
例如:
 
  1. MyClass * __weak myWeakReference;   
  2. MyClass * __unsafe_unretained myUnsafeReference;   
 
其他的變種在技術上是不正確的,但可能可以通過編譯。瞭解更多http://cdecl.org/
 
在棧上小心使用__weak。考慮下面的代碼:
  1. NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];   
  2. NSLog(@"string: %@", string);   
儘管string在初始化後被使用了。但是,由於在賦值的時候沒有強引用;因此它將立即被銷燬。
 
開發者同樣需要注意傳遞引用的地方,例如下面的代碼:
  1. NSError *error;   
  2. BOOL OK = [myObject performOperationWithError:&error];   
  3. if (!OK) {   
  4.     // Report the error.   
  5.     // ...   
 
然而,這種錯誤是隱含的。
  1. NSError * __strong e;   
並且聲明的方法可能是下面這樣:
  1. -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;   
編譯器將重寫代碼:
  1. NSError * __strong error;   
  2. NSError * __autoreleasing tmp = error;   
  3. BOOL OK = [myObject performOperationWithError:&tmp];   
  4. error = tmp;   
  5. if (!OK) {   
  6.     // Report the error.   
  7.     // ...   
本地變量聲明(__strong)和參數(__autoreleasing)導致編譯器創建臨時變量。當開發者對__strong對象取地址,將參數聲明爲id __strong*,就得到原始的指針。或者開發者可以將變量聲明爲__autoreleasing
 
使用Lifetime修飾符,避免循環引用
 
開發者可以通過lifetime修飾符來避免循環引用。例如,如果你有一個對象圖(關於父-子繼承)。並且父類需要引用子類,相反子類也要用。這時候,開發者把parent-to-child寫做strong並把child-to-parent關係寫作weak。其他情況可能有些麻煩,特別是調用block變量的時候。
 
手動引用計數模式下,__block id x 這樣的寫法,不會對x進行retain。在ARC模式,__block id x;默認retain x(就像其他變量一樣)。如果想在ARC模式下,得到手動引用計數的效果,開發者可以使用__unsafe_unretained __block id x。顧名思義,__unsafe_unretained __block id x是危險的(因爲他可能導致野指針)因此也不建議使用。好的解決方案是:使用__weak或者將__block的值設置爲nil,來打斷retain循環。
下面的代碼段展示瞭如何使用手動引用計數
  1. MyViewController *myController = [[MyViewController alloc] init…];   
  2. // ...   
  3. myController.completionHandler =  ^(NSInteger result) {   
  4.    [myController dismissViewControllerAnimated:YES completion:nil];   
  5. };   
  6. [self presentViewController:myController animated:YES completion:^{   
  7.    [myController release];   
  8. }];   
正如說的那樣,開發者可以使用__block修飾符並且設置myController變量爲nil,在完成處理的時候。
  1. MyViewController * __block myController = [[MyViewController alloc] init…];   
  2. // ...   
  3. myController.completionHandler =  ^(NSInteger result) {   
  4.     [myController dismissViewControllerAnimated:YES completion:nil];   
  5.     myController = nil;   
  6. };   
或者,你可以使用臨時的__weak變量。下面是一個簡單的實現
  1. MyViewController *myController = [[MyViewController alloc] init…];   
  2. // ...   
  3. MyViewController * __weak weakMyViewController = myController;   
  4. myController.completionHandler =  ^(NSInteger result) {   
  5.     [weakMyViewController dismissViewControllerAnimated:YES completion:nil];   
  6. };   
對於non-trivial循環,開發者應該使用下面代碼:
 
  1. MyViewController *myController = [[MyViewController alloc] init…];   
  2. // ...   
  3. MyViewController * __weak weakMyController = myController;   
  4. myController.completionHandler =  ^(NSInteger result) {   
  5.     MyViewController *strongMyController = weakMyController;   
  6.     if (strongMyController) {   
  7.         // ...   
  8.         [strongMyController dismissViewControllerAnimated:YES completion:nil];   
  9.         // ...   
  10.     }   
  11.     else {   
  12.         // Probably nothing...   
  13.     }   
  14. };   
 
在某些情況,開發者使用__unsafe_unretained,如果類本身不是__weak修飾。然而,這樣將變得不切合實際了,因爲它可能很難或者不可能去驗證:__unsafe_unretained的指針仍然可用並且指向某些變量。
 
ARC使用一個新語句管理Autorelease Pools
使用ARC,開發者不能直接使用NSAutoreleasePool來管理autorelease pools。而使用@autoreleasepool代替它。
  1. @autoreleasepool {   
  2.      // Code, such as a loop that creates a large number of temporary objects.   
  3. }   
這個簡單的結構允許編譯器思考引用計數的狀態。進入的時候,自動釋放池被push。在正常退出的時候自動釋放池配poped出來。爲了配合現有代碼,如果代碼異常退出,自動釋放池將不會pop出來。它比NSAutoreleasePool更有效率;因此建議開發者替換NSAtuoreleasePool。
 
Patterns for Managing Outlets Become Consistent Across Platforms(略)
 
棧變量被初始化爲nil
 
使用ARC,strong、weak和autoreleasing棧變量將不會顯示初始化爲nil,例如:
  1. - (void)myMethod {   
  2.     NSString *name;   
  3.     NSLog(@"name: %@", name);   
  4. }   
 
將打印出null,而不是崩潰。
 
使用編譯器選項來開啓和關閉ARC
 
開發者使用-fobjc-arc 編譯選項開啓ARC,還可以對某一個文件使用ARC,便於在使用手動引用計數的文件中使用ARC。對於已經使用ARC的工程,仍可以指定一個文件來關閉ARC通過-fno-objc-arc編譯選項。
 
管理Toll-Free Bridging
 
在多數的Cocoa程序中,開發者需要使用Core Foundaton-style對象,無論是從Core Foundation框架還是從Core foundation的其他框架比如Core Graphics。
 
編譯器不會自動管理Core foundation對象的生命週期。開發者必須根據COreFoundation的內存管理規則,使用CFRetain和CFRelease。
 
如果開發者在OC和Core foundation兩種對象做轉換,需要告訴編譯器對象的所有權。
 
● __bridge 不改變所有權的情況下,將OC和Core foundaton對象之間轉換。
 
● __bridge_retained 或者 CFBridgingRetain 或者對象的所有權,將OC和Corefoundaton對象之間轉換。開發者仍有責任將釋放對象通過CFRelease。
 
● __bridge_transfer 或者CFBridgingRelease將一個非OC指針,轉化爲OC指針,ARC負責釋放對象。
 
例如,現有代碼:
  1. - (void)logFirstNameOfPerson:(ABRecordRef)person {   
  2.     
  3.     NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);   
  4.     NSLog(@"Person's first name: %@", name);   
  5.     [name release];   
  6. }   
 
開發用下面的代碼代替
  1. - (void)logFirstNameOfPerson:(ABRecordRef)person {   
  2.     
  3.     NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));   
  4.     NSLog(@"Person's first name: %@", name);   
  5. }   
 
編譯器處理從COcoa方法返回的 CF 對象
 
編譯器知道返回Core foundaion類的OC的方法,遵循歷史的規定。例如,編譯器知道從CGColor方法返回的GCColor是不擁有的。開發者必須使用合適的類型去轉化。例如:
  1. NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];   
  2. [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];   
 
使用所有權關鍵字轉化函數參數
 
當開發者在OC和Core foundation的對象之間轉化時,需要告訴編譯器傳遞object的所有權。Core foundation對象所有權的規則在CoreFoundation的內存管理規則中。OC的所有權規則在Advanced Memory Management Programming Guide中。
 
下面的代碼段,數組傳遞給CGGradientCreateWithCorlors函數需要一個合適的轉化。數組是arrayWitshObjects返回的,所以,不要將所有權傳遞給函數,因此使用__bridge
  1. NSArray *colors = <#An array of colors#>;   
  2. CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);  
下面代碼段實現了一個context,使用Core Foundation內存管理的方法。
  1. - (void)drawRect:(CGRect)rect {   
  2.     CGContextRef ctx = UIGraphicsGetCurrentContext();   
  3.     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();   
  4.     CGFloat locations[2] = {0.0, 1.0};   
  5.     NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];   
  6.     [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];   
  7.     CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);   
  8.     CGColorSpaceRelease(colorSpace);  // Release owned Core Foundation object.   
  9.     CGPoint startPoint = CGPointMake(0.0, 0.0);   
  10.     CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));   
  11.     CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,   
  12.                                 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);   
  13.     CGGradientRelease(gradient);  // Release owned Core Foundation object.   
  14. }   
 
將工程轉化爲ARC,遇到的常見問題
 
當遷移現有的項目,你可能會遇到的各種問題。這裏有一些共同的問題,共同解決方案。
 
● 不能調用retain,release或者autorelease,這是一種特性,甚至可以這麼寫:
 
  1. while ([x retainCount]) { [x release]; }   
 
 
● 不能調用dealloc
通常,開發者在單例的實現或者替換一個對象的init方法的時候調用dealloc。對於單例模式,使用共享實例模式。在init方法中,你不用調用dealloc,因爲對象會被釋放,當重寫self的時候。
 
● 不能使用NSAutoreleasePool對象
 
表格@autoreleasepool{}代替NSAutoreleasePool.這將強制一個block處於一個自動釋放池中。它比NSAutoreleaePool快6倍。@autoreleasepool也在在非ARC模式下工作。
 
● ARC需要開發者對[super init]賦值給self在init方法中。
 
下面代碼是不可行的
  1. [super init];   
簡單的修正是:
  1. self = [super init];   
根號的修復是這樣的
 
  1. self = [super init];   
  2. if (self) {   
  3.    ...   
 
● 不能實現retain和release方法。
實現自定義的retain和release方法打破弱指針。提供了幾個常用的 “實現自定義”的原因。
 
1.性能
不要這麼做,NSObject的retain和release已經很快了。如果你發現仍有問題,請提出這個bug
 
2.實現一個自定義的弱指針系統
使用__weak代替
 
3.實現單例
使用shared instance pattern代替。或者使用類來代替方法,避免分配對象。
 
● assigned 將變成strong
 
在ARC之前,變量的assigned的不會延伸到對象的生命週期。爲了讓property變成強引用,開發者通常實例化或者synthesized訪問器方法,裏面是內存管理方法。相比之下,你可以這樣實現訪問器方法:
  1. @interface MyClass : Superclass {   
  2.     id thing; // Weak reference.   
  3. }   
  4. // ...   
  5. @end   
  6.     
  7. @implementation MyClass   
  8. - (id)thing {   
  9.     return thing;   
  10. }   
  11. - (void)setThing:(id)newThing {   
  12.     thing = newThing;   
  13. }   
  14. // ...   
  15. @end   
ARC下,實例中的變量默認是strong引用,assigning一個對象給實例中的變量延伸到對象的生命週期。遷移工具不能決定一個實例變量即將weak。保持相同的行爲之前,你必須標記實例變量是weak,或聲明它的property
 
  1. @interface MyClass : Superclass {   
  2.     id __weak thing;   
  3. }   
  4. // ...   
  5. @end   
  6.     
  7. @implementation MyClass   
  8. - (id)thing {   
  9.     return thing;   
  10. }   
  11. - (void)setThing:(id)newThing {   
  12.     thing = newThing;   
  13. }   
  14. // ...   
  15. @end   
 
或者
 
  1. @interface MyClass : Superclass   
  2. @property (weak) id thing;   
  3. // ...   
  4. @end   
  5.     
  6. @implementation MyClass   
  7. @synthesize thing;   
  8. // ...   
  9. @end   
 
● 不能使用strong ids在c的數據結構
例如,下面的c結構將不能編譯過
  1. struct X { id x; float y; };   
這是因爲x默認是strong retain,但是,在正常運行情況下,編譯器不能安全的合成所有需要的代碼。比如:如果你傳遞一個指針給x或者y後,執行了free,每一個id將被release掉,在struct被free之前。編譯器不能可靠的做這些。所以strong ids在結構,在ARC中完全不允許。下面是一些解決方案:
1.用OC類代替c結構。這應該是最好的解決辦法。
 
2.如果使用OC類是次要的方法(可能你想要一個高密度的結構數組),那麼考慮使用void*代替。這需要使用顯示的轉化。
 
3.把對象引用作爲__unsafe_unretained。這種方法是半常見的模式,這樣是有用的。
 
  1. struct x { NSString *S;  int X; } StaticArray[] = {   
  2.   @"foo", 42,   
  3.   @"bar, 97,   
  4. ...   
  5. };   
 
開發者這樣聲明
  1. struct x { NSString * __unsafe_unretained S; int X; }   
這可能是有問題的,如果對象可以被釋放時指針是不安全的,但它是非常有用的東西,被稱爲是字符串常量。
 
● 不能直接將id和void*進行轉化(包含Core Foundation類型)參考Managing Toll-Free Bridging
 
經常遇到的問題
 
我該怎樣認識ARC?它在哪兒放置retains/releases。
 
不要想什麼地方調用retain/release,考慮程序本身算法吧。考慮"strong and weak"關係,對象的所有權關係和可能存在的循環引用。
 
我還需要寫dealloc方法麼
可能需要。因爲ARC不會自動對 Core Foundation 對象,文件描述符等等進行malloc/free,,這些資源扔需要寫dealloc方法。
 
不不能release對象的變量,但是可以調用[self setDelegate:nil]在系統的類和其他不用ARC的代碼。
 
ARC種 不需要dealloc方法,或者[super dealloc];在運行時系統調用super代碼。
 
在ARC種,仍存在循環引用?
是的。ARC自動retain/release並可能繼承了循環引用的問題。幸運的是:遷移到ARC的代碼很少泄露,因爲無論properties是不是retain的,都被被聲明爲retain了。
 
在ARC種blocks是如何工作的
Block能正常工作,當你在棧上傳遞的時候,比如作爲返回值。你無需block copy了。當你傳遞block在棧下面的時候,添加到arrayWithObjects等需要retain的地方,需要使用[^{ }copy]。
 
有一件事情需要清除:NSString *__block myString是retain在ARC模式種,不可能是野指針。爲了得到以前的行爲,使用__block NSString *__unsafe_unretained myString 或者使用__block NSString *__weak myString.
 
我可以在雪豹OS種開發中使用ARC麼?
不行。雪豹版本的Xcode4.2不支持ARC。
 
ARC下,可以創建C語言的retain的指針數組麼
可以
 
  1. // Note calloc() to get zero-filled memory.   
  2. __strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));   
  3. for (int i = 0; i < entries; i++) {   
  4.      dynamicArray[i] = [[SomeClass alloc] init];   
  5. }   
  6.     
  7. // When you're done, set each entry to nil to tell ARC to release the object.   
  8. for (int i = 0; i < entries; i++) {   
  9.      dynamicArray[i] = nil;   
  10. }   
  11. free(dynamicArray);   
 
下面是一些注意的地方:
● 你要寫__strong SomeClass **在某些情況,默認情況是__autoreleasing SomeClass **.
● 開闢的內存必須是零填充
● 需要設置每一項爲nil在釋放array的時候(memset和bzero不好使的)
 
ARC慢麼?
取決於你怎麼測量,通常是不慢。編譯器消除很多無關緊要的retain/release調用。投入很大的努力在加快OC下的運行環境。尤其是返回一個retian/autoreleased對象,ARC並不是真正將它放到自動釋放池。
 
有一件事需要清除:優化器不在debug模式下。所以想看到更多的retain/release的調用,使用-O0比-Os
 
ARC在ObjC++模式下工作麼?
是的,甚至可以將strong/weakids在類和容器中。ARC編譯器合成retain/release邏輯在拷貝構造函數和析構函數鍾。
 
哪些類不支持weak引用?
下面的類不能創建弱引用:
NSATSTypesetter, NSColorSpace, NSFont, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, and NSTextView.
 
對於聲明properties時,你應該使用retain代替weak。對於變量你應該使用__unsafe_unretained代替__weak.另外,你不能創建這些類的弱引用:NSHashTable, NSMapTable, or NSPointerArray 
 
對NSCell和其他類使用NSCopyObject時候。
沒什麼特別的。ARC負責的顯示retain的場景。ARC模式下,所有的copy方法應該僅僅copy實例變量。
 
我可以對某個文件不使用ARC麼?
 
是的。當開發者遷移工程到ARC時,對所有OC源文件設置爲-fobjc-arc編譯選項。開發者可以指定一個文件設置-fno-objc-arc編譯選項。
 
GC(Grabage Collection)在mac上過時了?
Grabage Collection在10.8上已經過時,將來會重OS X中移除。ARC將是收推薦的替代技術。爲了幫助遷移現有程序,ARC遷移工具在Xcode >=4.3 支持將GC遷移到ARC。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章