天天看點

如何在項目中開始使用ARC(Automatic Reference Counting)

Automatic Reference Counting (ARC)是編譯器自動管理Objective-C對象的一個功能,相對于不得不考慮retain和release操作來說,ARC讓我們有更多的精力集中在我們應用内有趣的代碼、object graphs和對象之間的關系上。

如何在項目中開始使用ARC(Automatic Reference Counting)

概要

ARC是用過來在編譯的時候添加适當的代碼來保證對象在有用的時候有效,沒有了就不再有效了。從概念上講,ARC通過調用合适的記憶體管理方法遵循着和 manual reference counting(MRC)同樣的記憶體管理規則。

為了讓編譯器産生正确的代碼,ARC嚴格規定了你可以調用的方法和怎樣使用toll-free bridging。ARC引入了新的用于對象引用和屬性聲明的生命周期辨別符。

ARC支援Xcode 4.2 for OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5。弱引用不支援OS X v10.6 and iOS 4。

Xcode提供了一個工具用來把非ARC代碼自動轉換到ARC代碼(例如移除retain和release的調用),解決的不能自動遷移的問題。這個工具會把工程中的所有檔案都轉換成ARC,在使用非ARC友善的情況下,你也可以選擇某些檔案使用ARC。

ARC概況

不用再記住什麼時候該使用retain、release和autorelease了,ARC知道你的對象的使用周期然後在編譯的時候加入了合适的記憶體管理代碼。編譯器也會為你生成合适的dealloc方法。如果你剛剛使用ARC,那麼傳統的Cocoa的命名規範在你管理MRC的代碼的時候是非常重要的。

一個完整的正确的Person類的實作是這樣的:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *yearOfBirth;
@property Person *spouse;
@end
 
@implementation Person
@end
           

對象的屬性預設是strong的。

使用ARC,你可能實作這樣一個方法contrived :

- (void)contrived {
    Person *aPerson = [[Person alloc] init];
    [aPerson setFirstName:@"William"];
    [aPerson setLastName:@"Dudney"];
    [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];
    NSLog(@"aPerson: %@", aPerson);
}
           

ARC接管裡記憶體管理,是以Person和NSNumber都不會造成記憶體洩露。

你也可以安全的實作一個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之前不被釋放。

ARC強制的新規則

ARC提出了一些新的别的編譯器沒有的新規則。這些規定是為了能夠提供一個完整的可靠的記憶體管理模型。在一些情況能夠很簡單的提升性能,在一些情況下能夠簡化代碼或者讓你不用處理記憶體管理。如果你違背這些規則,在編譯的時候就會出錯,而不是在運作的時候産生錯誤。

  • 你不能顯示的調用dealloc,實作或者調用retain、release、retianCount或者autorealease。

             禁止使用  

@selector(retain)

@selector(release)等等

             如果你想要管理資源而不是釋放執行個體變量,你可以實作一個dealloc方法。你沒必要也不能釋放執行個體變量,但是你可以調用系統類或者非ARC代碼的                                                  [systemClassInstance setDelegate:nil]方法。              在ARC下實作的dealloc方法不能調用[super dealloc]方法,會導緻編譯錯誤。對于父類的調用是由編譯器自動和強制實作的。              你仍然可以調用CFRetian、CFRelease和一些與 Core Foundation-style對象有關的方法。

  • 你不能使用

    NSAllocateObject 和

    NSDeallocateObject

             你用alloc來建立對象,Runtime系統來負責釋放對象。

  • 在C結構體中不能使用對象指針。

             代替結構體,你可以使用一個Objective-C的類來管理資料

  • 不能随便的轉換id和void*

             你必須使用特殊的轉換來告訴編譯器對象的聲明周期,你必須在Objective-C對象和作為函數參數的Core Foundation類型之間這麼轉換。看下面的Toll-Free-Bridging。

  • 你不能是用NSAutoreleasePool

              ARC提供了@autoreleasepool塊來代替,比NSAutoreleasePool有更高的效率。

  • 你不能是用記憶體zones

              沒有必要再使用NSZone,它已經被Objective-C運作時系統忽略了。         為了能夠和手動管理記憶體的代碼互動,ARC提出了嚴格的命名規範。

  • 不能提供以new開頭的擷取方法,這意味着你不能添加一個以new開頭的屬性,而不自己添加get方法。
// Won't work:
    @property NSString *newTitle;
 
    // Works:
    @property (getter=theNewTitle) NSString *newTitle;
           

ARC提出了新的生命周期辨別符

ARC提出了幾個生命周期辨別符和弱引用。弱引用不會延長它指向的對象的生命周期。當沒有強引用指向這個對象的時候弱引用就會被指派為nil。 你應該在你的應用内充分利用這些辨別符來管理object graph。ARC不能防止循環引用的發生,但是我們可以明智的使用弱引用來避免循環引用發生。

Property屬性

weak和strong可以用作Preperty的屬性。就像下面這樣:

// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// The following declaration is similar to "@property(assign) MyClass *myObject;"
// except that if the MyClass instance is deallocated,
// the property value is set to nil instead of remaining as a dangling pointer.
@property(weak) MyClass *myObject;
           

在ARC下,strong是預設的對象類型。

變量辨別符

你可以像使用const一樣使用下面的變量辨別符:

__strong
__weak
__unsafe_unretained
__autoreleasing
           
  • __strong是預設的,一個對象隻要有__strong類型指針指向它,它就有效。
  • __weak聲明了一個引用并不持有該對象。當沒有強引用指向弱引用指向的對象的時候,弱引用被指派為nil。
  • __unsafe__unretain聲明了一個不持有對象的引用。當沒有強引用指向該對象的時候,該引用不會被指派為nil,是以當一個該引用指向的對象被回收的時候,該引用是危險的。
  • __autorelease是用來聲明通過id*傳遞并傳回autorelease的參數的。

你應該正确的使用辨別符,當聲明一個對象的變量的時候正确的做法如下:

ClassName * qualifier variableName;
           

例如:

MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;
           

其他變種寫法在技術上是不正确的,當時是被編譯器允許,想要了解更過可以看這裡: http://cdecl.org/ 考慮一下什麼時候在棧變量上使用__weak屬性,考慮下面的情況:

NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);
           

雖然string是在初始化指派之後使用的,但是在指派的時候沒有強引用指向string,是以它立刻就會被釋放。NSLog語句會輸出一個null值(在此例子中編譯器會給出警告)。 你同樣要注意通過引用傳遞的對象,下面的代碼是沒有問題的:

NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
    // Report the error.
    // ...
           

然而這個聲明是錯誤的而且是隐式的。

NSError * __strong e;
           

其實這個方法的聲明如下:

-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
           

編譯器重寫的代碼如下:

NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
    // Report the error.
    // ...
           

局部變量類型(__strong)和函數參數類型(__autorelease)不比對,就會引起編譯器生成一個臨時變量。當你使用__strong變量位址的時候你可以通過聲明參數為__strong來獲得原始指針。另外你也可以聲明為__autorelease。

使用生命周期辨別符避免循環引用

你可以使用生命周期辨別符來避免循環引用,例如,典型的你有這麼一個父子對象關系,父親需要引用他的孩子,反之亦然,然後你建立一個父親到孩子的強引用關系和孩子到父親的弱引用關系。其他的情況可能更微妙,特别是涉及到block objects的時候。 在MRC手動引用計數模型下,__block id x;對x不會有retaining影響。但是在ARC下,__block id x;預設的retaining x就像其他變量一樣。為了在ARC下獲得和MRC一樣的效果,你可以使用__unsafe__unretained __block id x;就像__unsafe__unretained名字表明的一樣,擁有一個non-retianed的變量是危險的,是以這個是不提倡的。兩個比較好的解決方案是使用__weak(如果你不需要支援iOS4或者OS X v10.6),或者設定__block變量為nil來打破循環引用。 下面的代碼片段說明了一個在MRC下有時會用到的模式:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
   [myController release];
}];
           

正像是描述的一樣,你可以使用__block辨別符,在completion handler内設定myController為nil:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;
};
           

另外,你可以使用一個臨時的__weak變量。下面的例子聲明了一個簡單是實作:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};
           

沒有了循環引用,但是你應該這樣用:

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...
    }
};
           

在一些情況下如果__weak是不相容的你可能使用__unsafe__unretained。然而對于循環引用這個可能變得不切實際,因為是非常難得或者不可能驗證__unsafe__unretained指針是有效的和指向了同樣有問題的對象。

ARC使用了新的語句來管理Autorelease Pools

使用ARC,你不能再使用NSAutoreleasePool類來管理記憶體池了。代替的是使用@autoreleasepool塊:

@autoreleasepool {
     // Code, such as a loop that creates a large number of temporary objects.
}
           

這個簡單的結構可以允許編譯器來推斷引用計數狀态。在入口處,一個autorelease pool被放進棧。在退出的地方(break,return,goto,fall-through等等)autorelease pool被彈出棧。為了相容已經存在的代碼,如果由于異常退出,autorelease pool是不會被彈出棧的。 這個文法是在所有的Objective-C模型中都可以用。這個是比NSAutoreleasePool更加有效率的。是以你應該在使用NSAutoreleasePool的地方使用它。

Patterns for Managing Outlets Become Consistent Across Platforms(管理Outlets)

在ARC下,iOS和OS X 中outlets的聲明改變了,變成了跨平台一緻的了。你應該使用的典型的模型是:outlets應該是weak的,除了來自nib檔案top-level對象的File`s Owner,應該是strong。

棧變量初始化為nil

使用ARC,strong,weak和autoreleasing 棧變量現在會被隐式的初始化為nil。例如:

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}
           

NSLog語句将會輸出null而不是crashing。

使用編譯标志來開啟和關閉ARC

你可以使用-fobjc-arc編譯标志來開啟ARC。如果一些檔案使用MRC更友善的話,你可以選擇按檔案來使用ARC。對于預設開啟ARC的項目來說,你也可以使用-fno-objc-arc來指定某些檔案不适用ARC。 ARC支援Xcode4.2和OS X v10.6之後的64位應用,或者iOS4之後的。弱引用不支援OS X v10.6和iOS4,是以在Xcode4.1之前都不支援ARC。

管理Toll-Free-Bridging

在許多的Cocoa應用中,你需要用到Core Foundation-style的對象,這些對象可能來自于Core Foundation架構本身例如CFArrayRef或者CFMutilDictionaryRef,或者是用來在Core Foundation 和 Core Graphics之間進行适配的架構例如CGColorSpaceRef和CGGradientRef。 編譯器不能夠自動管理Core Foundation對象的生命周期。你必須調用Core Foundation記憶體管理規則聲明的的CFRetain和CFRelease函數。 如果你想要在Objective-C對象和Core Foundation對象之間做轉換,你需要通過定義在objc/runtime.h中的轉換或者定義在NSObject.h中的Core Foundation-style宏告訴編譯對象的持有關系。

  • __bridge 用來在Objective-C對象和Core Foundation對象之間轉換,不改變持有關系。
  • __bridge_retained或者CFBridgingRetain 用來将Objective-C對象轉換成Core Foundation對象,傳遞持有關系給你,你需要負責調用CFRelease或者相關函數放棄持有關系
  • __bridge_transfer或者CFBridgingRelease用來将非Objective-C對象轉換成Objective-C對象,傳遞對象持有關系給ARC。ARC負責放棄持有關系。

例如你可能寫過這樣的代碼:

- (void)logFirstNameOfPerson:(ABRecordRef)person {
 
    NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSLog(@"Person's first name: %@", name);
    [name release];
}
           

現在替換成這樣:

- (void)logFirstNameOfPerson:(ABRecordRef)person {
 
    NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    NSLog(@"Person's first name: %@", name);
}
           

編譯器處理Cocoa方法傳回的CF對象

編譯器知道傳回CF對象的Cocoa方法遵守着曆史Cocoa命名規約。Core Foundation-style對象的持有關系詳細的闡述在CF記憶體管理規則裡面。 下面的代碼片段,傳給 CGGradientCreateWithColors方法的數組需要合适的轉換。 arrayWithObjects:傳回的對象的持有關系沒有傳遞給該方法,是以僅需要__bridge。

NSArray *colors = <#An array of colors#>;
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
           

在下面的方法實作中的代碼片段,可以注意下CF記憶體管理規則聲明的記憶體管理方法的使用。

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {0.0, 1.0};
    NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
    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.
}
           

轉換工程可能遇到的問題

在轉換存在的工程的時候你可能遇到各種問題,這裡列出了一些問題和答案。

  • 你不能調用retian、release或者autorelease

              這樣會失敗,你也不能這樣寫:

while ([x retainCount]) { [x release]; }
           
  • 你不能調用dealloc

              如果你實作單例或者實作了一個類的init方法,通常會調用dealloc。你不能再在init方法中調用dealloc,因為當你覆寫self的時候,這個對象會被釋放。

  • 不能再使用NSAutoreleasePool對象

             代替使用@autoreleasepool{}塊。在autorelease pool上的塊結構的速度是NSAutoreleasePool的6倍。@autoreleasepool可以工作在非ARC下。正是因為                                    @autoreleasepool比NSAutoreleasePool快很多,是以許多老的性能問題可以無條件的用@autoreleasepool來替換。項目轉換隻能解決簡單的NSAutoreleasePool的                使用,不能處理複雜的情況,或者是變量定義在@autoreleasepool内,然後在之後使用的情況。

  • ARC需要你在init方法中把[supre init]的結果指派給self。

              下面的代碼在ARC下是無效的:

[super init];
           

              簡單的修改如下:

self = [super init];
           

              更好的處理是,檢查self是否為空之後再繼續操作:

self = [super init];
       if (self) {
            ...
           

(以下紅字部分為自己添加)

  • init開頭的方法要傳回執行個體對象
    id obj = [[NSObject alloc] init];
               
    init方法會初始化alloc方法傳回的對象,然後原封不動的傳回給調用者。
    - (void)initThisObect;
               
    這個方法聲明就不對。隻有一個方法例外:
    - (void)initialize;
               
  • 屬性聲明為readonly

    屬性聲明為readonly,而沒有添加任何其他方法的話,不管是在類内還是類外都無法對該屬性指派。

    @property (nonatomic,readonly,strong) NSString *str;
               

    屬性會自動生成一個成員變量_str,一般情況下會生成該成員變量的get和set方法,但是readonly表示隻生成了get方法而沒有生成set方法,如果我們想在自己的類内對該屬性進行賦,我們可以通過以下兩種方式聲明私有的set方法,然後我們就可以在自己的類内對readonly屬性就行指派啦。

    在類的擴充中重新聲明一個readwrite屬性,隻要名字相同該屬性同樣會對應到_str成員變量:

    @property (nonatomic,strong,readwrite) NSString *str;
               
    聲明一個私有的set方法:
    - (void)setStr:(NSString *)tempStr
    {
        if (tempStr != _str)
        {
            _str = tempStr;
        }
    }
               
  • 你不能實作retain或者release方法

              自己實作retain和release方法破壞了弱指針,在以下幾種情況下可以實作自己的方法:

    1. 性能。不要再這麼做了,因為NSObject的retain和release的實作現在已經非常快了。如果你任然發現問題,請送出給蘋果Bug。
    2. 自己實作弱指針。請使用weak。
    3. 單例類的實作。請使用單例模式,使用類方法代替執行個體方法,那樣就避免了不得不申請對象。              
  • “Assigned”變量變strong

              在ARC之前,執行個體變量是沒有持有關系的,直接指派給執行個體變量是不會延長對象的生命周期的。聲明一個strong屬性,會實作或者同步一個調用了合适的記憶體管理方法                 的擷取方法。相反,你可能實作過下面這樣的代碼來維護弱指針。

@interface MyClass : Superclass {
    id thing; // Weak reference.
}
// ...
@end
 
@implementation MyClass
- (id)thing {
    return thing;
}
- (void)setThing:(id)newThing {
    thing = newThing;
}
// ...
@end
           
在ARC下,執行個體變量預設是強引用。指派給執行個體變量會延長對象的生命周期。轉換工具不知道執行個體變量是否為弱引用。為了能夠擁有和之前一樣的功能,你需要使用weak或者使用屬性。
@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
           
  • 在C結構體中不能使用ids。
例如下面的代碼不能編譯:
struct X { id x; float y; };
           
因為X預設是請持有的,編譯器不能安全的實作所有使之能正确運作的代碼。例如通過一些代碼傳遞指針給這些結構體的一些然後結束的時候釋放。每一個id不得不在struct釋放之前釋放。編譯器不能依靠這個,是以在ARC下結構體内禁止使用強id。有一下幾個解決方案:
  1. 使用Objective-C對象代替結構體。這是最好的方式。
  2. 如果Objective-C對象是次優的選項,可以考慮使用void *。需要明确的轉換,下面描述。
  3. 标記對象為__unsafe_unretained。這個方法對下面這個不常見的模式可能是有用的:
struct x { NSString *S;  int X; } StaticArray[] = {
  @"foo", 42,
  @"bar, 97,
...
};
           
可以聲明結構體如下:
struct x { NSString * __unsafe_unretained S; int X; }
           
這個可能是有問題的和不安全的,如果結構體下的對象釋放了的話。但是對于永遠字元串常量是非常有用的。
  • 不能在id和void*之間進行轉換

一些常問的問題(不譯)