Transitioning to ARC Release Notes

ARC (Automatic Reference Counting)是llvm compiler的特性之一,提供了objective-c对象的自动内存管理功能。它自动在适当的地方插入retianreleaseautorelease方法,配合autoreleasepool使用。启用了ARC之后,将不再能手动使用retianreleaseautorelease方法了。

ARCoverview

Summary

ARC通过在适当的地方插入retain、release命令来确保objects在需要使用的情况下推迟释放,但不会推迟太久。基于reference counting(详见 Advanced Memory Management Programming Guide )。

为了让compiler生成正确地内存管理代码,启用ARC后将会让retain、release、autorelease还有一些tool-free bridging方法(详见下面的 Toll-Free Bridged Types)受限。同时ARC还引入了一些object生命周期管理修饰符。

ARC is supported in Xcode 4.2 for OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5. Weak references are not supported in OS X v10.6 and iOS 4.

Xcode提供非ARC模式到ARC模式的代码装换工具,还可以指定某一个文件不启用ARC,如果你觉得手动管理内存更加高效的话。

Xcode provides a tool that automates the mechanical parts of the ARC conversion (such as removing retain and release calls) and helps you to fix issues the migrator can’t handle automatically (choose Edit > Refactor > Convert to Objective-C ARC). The migration tool converts all files in a project to use ARC. You can also choose to use ARC on a per-file basis if it’s more convenient for you to use manual reference counting for some files.

ARC Overview

ARC 自动检测objects的生命周期然后在正确地地方插入内存管理指令,这一过程在编译时完成。compiler还会生成object的dealloc方法。

举个栗子,一个Person类代码如下:

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


@implementation Person
@end

object中声明的property默认是带有strong修饰符的(强引用),详见后面的 ARC Introduces New Lifetime Qualifiers

使用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都不会产生内存泄露。

- (void)takeLastNameFrom:(Person *)person {
    NSString *oldLastname = [self lastName];
    [self setLastName:[person lastName]];
    NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
}

ARC保证lastname在NSLog完之前不被释放。

ARC Enforces New Rules

Rules如下:

  • 不能直接引用dealloc,不能实现或引用retainreleaseretianCountautorelease。当然@selector(retain),@selector(release)548/6是不行的。自定义的 dealloc方法里面不要使用[super dealloc],编译器会自动完成。CFRetain、CFRelease等Core Foundation框架的方法可用,应为ARC只对object-c object生效(详见 Managing Tool-Free Bridging )。
  • 不能使用NSAllocateObject、NSDeallocateObject。仍然可以使用alloc创建object,ARC会自动释放。
  • 不能在C结构体中使用object pointers,转为使用objective-c来封装。
  • id与void *之间的临时互转。当我们在Core Foundation的C函数和Foundation Kit的Objective-C方法间传递对象时,常常需要进行id和void *两个类型的互转。叫做免费桥接(Toll Free Bridging)。如果使用ARC,必须在CF对象进入和脱离ARC的控制时,用提示/限定符来告知编译器。限定符有__bridge、__bridge_retain和__bridge_transfer。另外,仍需要用CFRetain和CFRelease来管理Core Foundation的对象。
  • 不能使用NSAutoreleasePool object,改为使用@autoreleasepool blocks。
  • 不能使用memory zones。基于Zone的内存已经没了(在运行时里也没了)。不能再使用NSAllocateObject和NSDeallocateObject。

还有注意一下这个:

// Won't work:
@property NSString *newTitle;


// Works:
@property (getter=theNewTitle) NSString *newTitle;

ARC Introduces New Lifetime Qualifiers

ARC并不帮你解决strong reference cycles,还是要使用weak修饰符解决。

Property Attributes

property关键字的 weak 和 strong :

// 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;

Variable Qualifiers

变量修饰符:

//默认,只要object有一个以上指向它的强引用指针,它就会留在内存里不被释放。
__strong

//弱引用不会retain object被释放之后指针变nil
__weak

//比起__weak,object被释放之后不会自动变成nil
__unsafe_unretained

//和autorelease方法一样
__autoreleasing

格式:

ClassName * qualifier variableName;

MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;

注意__weak修饰符的使用:

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

以上代码会输出null,应为NSString木有一个strong reference,创建之后会被马上释放。compiler会报warning错。

另一个栗子

调用方代码:

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

在这里error的声明默认是使用 __strong 修饰符的,声明实际上是这样的:

NSError * __strong e;

还有performOperationWithError:方法声明一般是这样的:

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

这个时候compiler会rewrite code变成:

NSError * __strong error;
NSError * __autoreleasing tmp = error;

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

因为local variable和方法参数的修饰符不一样,compiler就会创建中间变量。想要省去这个中间变量,你可以修改方法的参数修饰符为__strong,或者修改error object的修饰符为__autorelease。

Use Lifetime Qualifiers to Avoid Strong Reference Cycles

MRR(manual reference counting)模式下, __block id x; 不会retain x,但是ARC模式下默认会retain。如果你不想让它retain,在前面加上 __unsafe_unretained, 变成 __unsafe_unretained __block id x; 。为了防止使用修饰符__unsafe_unretained 的指针指向object被释放掉,改为使用__weak或者把__block变量指针设置为nil,看下面栗子。

MRR:

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

ARC:

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

non-trivial cycles:

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

一些特殊情况下你可以需要使用__unsafe_unretained,但是要注意检查。

ARC Uses a New Statement to Manage Autorelease Pools

不能直接使用NSAutoreleasePool类,改为使用 @autoreleasepool block:

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

Patterns for Managing Outlets Become Consistent Across Platforms

一般来说outlet都使用weak修饰。详见 《 Resource Programming Guide 》。

Stack Variables Are Initialized with nil

使用了修饰符的变量默认初始化为nil:

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

输出null

Use Complier Flags to Enable and Disable ARC

使用 -fobjc-arc 开启ARC。

使用 -fno-objc-arc 关闭ARC。

Managing Toll-Free Bridging

ARC不对Core Foundation框架负责,所以还是使用CFRetain、CFRelease来管理内存。详见 《 Memory Management Programming Guide for Core Foundation 》。

在Cocoa框架中使用Core Foundation objects的情况,你需要使用下面的一些修饰符或者相关函数(为啥叫cast?)来告诉编译器object的持有情况:

  • __bridge 转换pointer类型但是不转换ownership
  • __bridge_retained 或者 CFBridgingRetain 转换pointer类型同时retain object
  • __bridge_transfer 或者 CFBridgingRelease 装换pointer类型同时retain object,ARC会在之后自动释放这次retain

栗子:

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

使用CFBridgingRelease之后:

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

The Complier Handles CF Objects Returned From Cocoa Methods

因为一些历史原因,某些Cocoa框架下的方法会返回Core Foundation object(详见 《 Advanced Memory Management Programming Guide 》)。举个例,compiler知道UIColor的CGColor方法返回的CGColor不是自己持有(owned)的。所以你还是需要做一些必要的转换,如果你要retain的话:

NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
[colors addObject:(id)[[UIColor lightGrayColor] CGColor]];

Cast Function Parameters Using Ownership Keywords

使用 __bridge:

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

下面栗子使用CF内存管理函数,调用了CGGradientCreateWithColors:

- (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 |CGGradientDrawsAfterEndLocation);
    CGGradientRelease(gradient);  // Release owned Core Foundation object.
}

Common Issues While Converting a Project

没有写过什么old project……

不看

Frequently Asked Questions

不番了:

  • How do I think about ARC? Where does it put the retains/releases?

Try to stop thinking about where the retain/release calls are put and think about your application algorithms instead. Think about “strong and weak” pointers in your objects, about object ownership, and about possible retain cycles.

  • Do I still need to write dealloc methods for my objects?

Maybe.

Because ARC does not automate malloc/free, management of the lifetime of Core Foundation objects, file descriptors, and so on, you still free such resources by writing a dealloc method.

You do not have to (indeed cannot) release instance variables, but you may need to invoke [self setDelegate:nil] on system classes and other code that isn’t compiled using ARC.

dealloc methods in ARC do not require—or allow—a call to [super dealloc]; the chaining to super is handled and enforced by the runtime.

  • Are retain cycles still possible in ARC?

Yes.

ARC automates retain/release, and inherits the issue of retain cycles. Fortunately, code migrated to ARC rarely starts leaking, because properties already declare whether the properties are retaining or not.

  • How do blocks work in ARC?

Blocks “just work” when you pass blocks up the stack in ARC mode, such as in a return. You don’t have to call Block Copy any more.

The one thing to be aware of is that NSString * __block myString is retained in ARC mode, not a possibly dangling pointer. To get the previous behavior, use __block NSString * __unsafe_unretained myString or (better still) use __block NSString * __weak myString.

  • Can I develop applications for OS X with ARC using Snow Leopard?

No. The Snow Leopard version of Xcode 4.2 doesn’t support ARC at all on OS X, because it doesn’t include the 10.7 SDK. Xcode 4.2 for Snow Leopard does support ARC for iOS though, and Xcode 4.2 for Lion supports both OS X and iOS. This means you need a Lion system to build an ARC application that runs on Snow Leopard.

  • Can I create a C array of retained pointers under ARC?

Yes, you can, as illustrated by this example:

// Note calloc() to get zero-filled memory.
__strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));
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);
There are a number of aspects to note:

You will need to write __strong SomeClass ** in some cases, because the default is __autoreleasing SomeClass **.
The allocated memory must be zero-filled.
You must set each element to nil before freeing the array (memset or bzero will not work).
You should avoid memcpy or realloc.

  • Is ARC slow?

It depends on what you’re measuring, but generally “no.” The compiler efficiently eliminates many extraneous retain/release calls and much effort has been invested in speeding up the Objective-C runtime in general. In particular, the common “return a retain/autoreleased object” pattern is much faster and does not actually put the object into the autorelease pool, when the caller of the method is ARC code.

One issue to be aware of is that the optimizer is not run in common debug configurations, so expect to see a lot more retain/release traffic at -O0 than at -Os.

  • Does ARC work in ObjC++ mode?

Yes. You can even put strong/weak ids in classes and containers. The ARC compiler synthesizes retain/release logic in copy constructors and destructors etc to make this work.

  • Which classes don’t support weak references?

You cannot currently create weak references to instances of the following classes:

NSATSTypesetter, NSColorSpace, NSFont, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, and NSTextView.

Note: In addition, in OS X v10.7, you cannot create weak references to instances of NSFontManager, NSFontPanel, NSImage, NSTableCellView, NSViewController, NSWindow, and NSWindowController. In addition, in OS X v10.7 no classes in the AV Foundation framework support weak references.
For declared properties, you should use assign instead of weak; for variables you should use __unsafe_unretained instead of __weak.

In addition, you cannot create weak references from instances of NSHashTable, NSMapTable, or NSPointerArray under ARC.

  • What do I have to do when subclassing NSCell or another class that uses NSCopyObject?

Nothing special. ARC takes care of cases where you had to previously add extra retains explicitly. With ARC, all copy methods should just copy over the instance variables.

  • Can I opt out of ARC for specific files?

Yes.

When you migrate a project to use ARC, the -fobjc-arc compiler flag is set as the default for all Objective-C source files. You can disable ARC for a specific class using the -fno-objc-arc compiler flag for that class. In Xcode, in the target Build Phases tab, open the Compile Sources group to reveal the source file list. Double-click the file for which you want to set the flag, enter -fno-objc-arc in the pop-up panel, then click Done.

configpic

标签:ios, object-c, arc