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 {…}
@endwill 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:
Nice post.
Detailed post. I like it. Thanks :)
Post a Comment