Thursday, June 30, 2011

WWDC2011 Session 323 Introducing Automatic Reference Counting

Summary: Automatic Reference Counting (ARC) dramatically simplifies memory management in Objective-C. This session introduces what ARC is, how it is implemented and how to migrate to ARC. Apple asks every developer to move Objective-C code to ARC.

Problems of memory management:

Many (new) developers do not understand reference counting. They don’t know when to retain/release/autorelease. Memory leaks and the app crashes.

What is ARC?

ARC is automatic object memory management. The compiler adds retain/release calls. It still uses reference counting. ARC is not Garbage collection. ARC is compile-time memory management, not run-time memory management.

If we write a stack, our code without ARC will be:

@implementation Stack { NSMutableArray *_array; }

- (id) init {

if (self = [super init])

_array = [[NSMutableArray array] retain];

return self;

}

- (void) push: (id) x {

[_array addObject: x];

}

- (id) pop {

id x = [[_array lastObject] retain];

[_array removeLastObject];

return [x autorelease];

}

- (void) dealloc { [_array release]; [super dealloc]; }

@end

With ARC:

@implementation Stack { NSMutableArray *_array; }

- (id) init {

if (self = [super init])

_array = [NSMutableArray array];

return self;

}

- (void) push: (id) x {

[_array addObject: x];

}

- (id) pop {

id x = [_array lastObject];

[_array removeLastObject];

return x;

}

@end

How do you switch?

1. Remove code of retain/release/autorelease/retainCount.

2. No object pointers in C structs

Compiler must know when references come and go. Pointers must be zero initialized. Release when reference goes away.

For example, do not write code like this:

struct Pair {

NSString *Name;

int Value;

};

Solution: Just use objects

3. No casual casting id <-> void*

Compiler must know whether void* is retained

Now CF conversion APIs

Three keywords to disambiguate casts

CFStringRef W = (__bridge CFStringRef)A;

NSString *X = (__bridge NSString*)B;

CFStringRef Y = (__bridge_retain CFStringRef)C;

NSString *Z = (__bridge_transfer NSString*)D;

Also discussed in WWDC2011 Session Objective-C Advancements In-Depth.

4. No NSAutoreleasePool

You can see autoreleasepool in main

int main(int argc, char *argv[]) {

@autoreleasepool {

return UIApplicationMain(argc, argv, nil, nil);

}}

You have known ARC. If you are not interested in the implementation of ARC, you can skip the following content and jump to the end to look at “Migrating to ARC”.

How does it work?

Strong references

A variable is strong reference by default, like a retain property.

NSString *name; equals to __strong NSString *name;

A variable is initialized to nil.

The following code

{

NSString *name;

name = oldName;

}

is changed by the compiler to (the orange code is inserted by the compiler):

{

__string NSString *name = nil;

name = [oldName retain];

[name release];

}

Unsafe References

Just C-style assignment. No extra logic.

__unsafe_unretained NSString *unsafeName = name;

Weak References

The weak reference does not retain, but the pointer will be nil as soon as object starts deallocation.

For example, the following code

__weak NSString *weakName = name;

char c = [weakName characterAtIndex: 0];

is changed by the compiler to (the orange code is inserted by the compiler):

__weak NSString *weakName = nil;

objc_storeWeak(&weakName, name);

char c = [objc_readWeak(&weakName) characterAtIndex: 0];

objc_storeWeak(&weakName, nil);

Autoreleasing References

Describes out-parameters. Only on the stack. Not for general use.

For example, the following code

- (void) runWthError: (NSError **)err {

if (!valid_)

*err = …;

}

is changed by the compiler to (the orange code is inserted by the compiler):

- (void) runWithError:(__autoreleasing NSError **) err {

if (!valid_)

*err = [[… retain] autorelease];

}

Return Values

Does this transfer ownership?

alloc, copy, init, mutableCopy, new transfer ownership. Everything else does not.

Normal Returns (autorelease)

For example, the following code

- (NSString*)serial {

return _serial;

}

is changed by the compiler to (the orange code is inserted by the compiler):

- (NSString*)serial {

NSString *returnValue = [_serial retain];//Vince: Why retain

return [returnValue autorelease];

}

No transfer. Retain immediately. Autorelease after leaving all scopes.

Retained Returns

For example, the following code

- (NSString*)newSerial {

return _serial;

}

is changed by the compiler to (the orange code is inserted by the compiler):

- (NSString*)newSerial {

NSString *returnValue = [_serial retain];

return returnValue;

}

Passes back ownership.

Accepting a Retained Return

For example, the following code

- (void) logSerial {NSLog(“%@\n”, [self newSerial]);}

is changed by the compiler to (the orange code is inserted by the compiler):

- (void) logSerial {

NSString *returnValue = [self newSerial];

NSLog(“%@\n”, returnValue);

[returnValue release];

}

Takes ownership. Return value released at end of statement.


Migrating to ARC

Migration Steps

- Compile your code with the LLVM compiler 3.0

- Use “Edit->Refactor->Convert to Objective-C ARC…” command in Xcode 4.2+

- Fix issues until everything compiles

- Migration tool then modifies your code and project

Two Phases of ARC Migration

- Analysis

Migration programs presented as compile errors

Fix errors, run analysis again

- Conversion

After successful analysis, changes automatically applied

Turns on -fobjc-arc

Common Conversion

Autorelease pool:

- (void)encodeFile {

/* setup */

BOOL done = NO, shouldDrain = NO;

NSAutoreleasePool *loopPool = [NSAutoreleasePool new];

while (!done) {

/* part A. */

if (shouldDrain) {

  [loopPool drain];

  loopPool = [NAutoreleasePool new];

}

/* part B. */

[loopPool drain];

}

will be changed to:

- (void)encodeFile {

/* setup */

BOOL done = NO;

while (!done) {

@autoreleasepool {

/* part A. */

/* part B. */

}

}

}

Singleton Pattern

@implementation ActivityIndicator

- (id)retain { return self; }

- (oneway void)release {}

- (id)autorelease { return self; }

- (NSUInteger) retainCount { return NSUIntegerMax; }

+ (ActivityIndicator *)sharedIndicator {…}

- (void)show {…}

- (void)hide {…}

@end

will be changed to:

@implementation ActivityIndicator

+ (id)allocWithZone:(NSZone *)zone {

static id sharedInstance;

if (sharedInstance == nil)

sharedInstance = [super allocWithZone:NULL];

return sharedInstance;

}

+ (ActivityIndicator *)sharedIndicator {

static ActivityIndicator *sharedIndicator;

if (sharedIndicator == nil) sharedIndicator = [ActivityIndicator new];

return sharedIndicator;

}

- (void)show {…}

- (void)hide {…}

@end


I can also use dispatch_once for thread-safety

+ (ActivityIndicator *)sharedIndicator {

static ActivityIndicator *sharedIndicator;

static dispatch_once_t done;

dispatch_once(&done, ^{ sharedIndicator = [ActivityIndicator new]; })

return sharedIndicator;

}

static NSInteger count;

+ (void)show {

if (count++ == 0)

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

}

+ (void)hide {

if (count && —count == 0)

[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

}

Delegate pattern

@protocol PageViewDelegate;

@interface PageView : NSView {

id <PageViewDelegate> delegate;

/* … */

}

@property(assign) id <PageViewDelegate> delegate;

@end


@implementation PageView

@synthesize delegate;

/* … */

@end

will be changed to:

@protocol PageViewDelegate;

@interface PageView : NSView {

__weak id <PageViewDelegate> delegate;

/* … */

}

@property(weak) id <PageViewDelegate> delegate;

@end


@implementation PageView

@synthesize delegate;

/* … */

@end

ARC Deployment

- Mac OS X Lion 10.7 and iOS 5

- We can let ARC code run on older versions of Mac OS X and iOS

- Automatically linked into your application if needed. (Selected by deployment target)

- However, you cannot use weak references on Mac OS 10.6/iOS4. (My understanding is week references need run-time support. So on older versions of Mac OS X and iOS, we can use pure compile-time features of ARC.) Migration tool will user__unsafe_unretained (not weak) for assign properties.


WWDC2011 Session 323 Introducing Automatic Reference Counting

Author Chris Lattner

2 comments:

Anonymous said...

Nice post.

Anonymous said...

Detailed post. I like it. Thanks :)