Monday, February 6, 2012

AUTOMATIC REFERENCE COUNTING IN OBJECTIVE-C

Automatic Reference Counting (ARC) largely removes the burden of manual memory management, not to mention the chore of tracking down bugs caused by leaking or over-released objects! Despite its awesomeness, ARC does not let you ignore memory management altogether.
This post covers the following key aspects of ARC to help you get up and running quickly.
  1. Reference Counted Memory: Quick Revision
  2. How Automatic Reference Counting Works
  3. Enabling ARC in Your Project
  4. New Rules Enforced by ARC
  5. ARC Qualifiers – Declared Properties
  6. ARC Qualifiers – Regular Variables
  7. Migrating Existing Projects to ARC
  8. Including Code that is not ARC Compliant
  9. Should I Use ARC?

What Has Changed?

ARC Related changes to Xcode 4.2
In the time before ARC, you had to manually retain/release/autorelease objects to ensure they would “stick around” for as long as you needed them. Forgetting to send retain to an object, or releasing it too many times would cause your app to leak memory or crash.
In Xcode 4.2, in addition to syntax checking as you type, the new Apple LLVM compiler makes it possible to offload the burden of manual memory management to the compiler, introspecting your code to decide when to release objects. Apple’s documentation describes ARC as follows:
“Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.”
This feature makes the memory management trivial most of the time, but you still need to take some responsibility for how your classes manage references to other objects.
Let’s start at the beginning …

Reference Counted Memory: Quick Review

Manually managed, reference counted memory works in iOS as follows: when creating an object using alloc/init (or other similar method), the object is returned with a retainCount of 1, meaning you havetaken ownership of the object.
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release];
In between alloc/init’ing an object (i.e. taking ownership) and releasing an object (i.e. relinquishing ownership), you can do with it as you wish, safe in the knowledge the object will not be deallocated whilst still in use.
Similarly, by adding an object to an autorelease pool, your object will stick around when needed and be deallocated sometime in the future when no longer needed.
1
2
3
4
-(NSObject*) someMethod {
  NSObject *obj = [[[NSObject alloc] init] autorelease];
  return obj; // will be deallocated by autorelease pool later
}

How Automatic Reference Counting Works

Most programmers new to iOS have trouble getting their heads around reference counted memory. ARC is a pre-compilation step that adds retain/release/autorelease statements to your code for you.
This is not Garbage Collection, and reference counted memory has not disappeared, it has simply been automated. It may sound like a bit of an after thought, but considering how many features in Objective-C are implemented by pre-processing source files before compiling, ARC is really par for the course.
When writing code with ARC enabled your code will look like this:
1
2
NSObject *obj = [[NSObject alloc] init];
// do some stuff
The ARC pre-compilation step will auto-magically turn it into this:
1
2
3
NSObject *obj = [[NSObject alloc] init];
// do some stuff
[obj release]; // **Added by ARC**
The diagram below (from Apple’s documentation) seems to imply that writing operative code takes almost as long to write as retain/release logic. Whilst this is not true for experienced Objective-C coders, it is probably a conservative estimate if you are new to Objective-C. Also, when they do pop-up, memory problems can be a PITA to track down.
Source: Programming With ARC Release Notes

Enabling ARC in Your Project

To enable ARC simply set the Objective-C Automatic Reference Counting option in your Xcode project’s Build Settings to YES. Behind the scenes this sets the -fobjc-arc compiler flag that enables ARC.

New Rules Enforced by ARC

There are a few rules you need to abide by in order to compile with ARC enabled.
1. Alloc/Init Objects
As described above, creating objects is done as before, however you must not make anyretain/release/autorelease/retainCount calls. You also cannot be sneaky by calling their selectors indirectly: use of @selector(retain) and @selector(release) are prohibited.
2. Dealloc Methods
Generally these will be created for you. You must not make a dealloc call directly. However you can still create a custom dealloc method if you need to release resources other than instance variables. When creating a custom dealloc method, do not call the [super dealloc] method. This will be done for you and is enforced by the compiler.
3. Declared Properties
Before ARC, we told the compiler how to memory manage declared public properties using theassign/retain/copy parameters with the @property directive. These parameters are no longer used in ARC. Instead we have the weak/strong parameters to tell the compiler how we want our properties treated.
4. Object Pointers in C Structures
This is also a taboo. The docs suggest storing them in a class instead of a struct. This makes sense since they would otherwise be unknown to ARC. It might cause extra some migration headaches. However, ARC can be disabled on a file by file basis. See the section on “Including Code that is not ARC Compliant” below.
5. Casual Casting Between id and void*
Casting between id and void* data types is frequently done when handing objects between Core Foundation’s C library functions and Foundation Kit’s Objective-C library methods. This is known asToll Free Bridging.
With ARC you must provide hints/qualifiers to tell the compiler when CF objects are moving into and out of its control for memory management. These qualifiers include __bridge, __bridge_retain and__bridge_transfer. Also you still need to call CFRetain and CFRelease to memory manage Core Foundation objects.
This is a more advanced topic, so if you don’t know what CF objects are, don’t worry for now.
6. @autoreleasepool instead of NSAutoReleasePool
ARC compliant code must not use NSAutoReleasePool objects, instead use the @autoreleasepool{}blocks. A good example is in the main.m file of any ARC compliant/migrated project.
1
2
3
4
5
6
int main(int argc, char *argv[])
{
  @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([ExampleAppDelegate class]));
  }
}
7. Other Gotchas
Zone based memory is gone (apparently it’s not in the runtime anymore either); you cannot use NSAllocateObject or NSDeallocateObject.

ARC Qualifiers – Declared Properties

As programmers, we are used to making decisions like whether to make something a variable or a constant, locally or globally defined, etc. So too, we must decide how our properties relate to other objects. We use the strong/weak qualifiers to notify the compiler of these relationships.
Strong References
A strong reference is a reference to an object that stops it from being deallocated. In other words it creates a owner relationship. Whereas previously you would do this:
1
2
// Non-ARC Compliant Declaration
@property(retain) NSObject *obj;
Under ARC we do the following to ensure a class instance takes an ownership interest a referenced object (i.e. so it cannot be deallocated until the owner is).
1
2
// ARC Compliant Declaration
@property(strong) NSObject *obj;
Weak References
A weak reference is a reference to an object that does not stop it from being deallocated. In other words, it does not create an owner relationship. Whereas previously you would do this:
1
2
// Non-ARC Compliant Declaration
@property(assign) NSObject *parentObj;
Under ARC you would do the following to ensure you do not “own” the object being referred to (e.g. typically a child object should not own its parent, so we would use a weak reference).
1
2
// ARC Compliant Declaration
@property(weak) NSObject *parentObj;

ARC Qualifiers – Regular Variables

Variable Qualifiers
The above examples illustrate declaring how our declared properties should be managed. For regular variables we have:
1
2
3
4
__strong
__weak
__unsafe_unretained
__autoreleasing
Generally speaking, these extra qualifiers don’t need to be used very often. You might first encounter these qualifiers and others when using the migration tool. For new projects however, you generally you won’t need them and will mostly use strong/weak with your declared properties.
  • __strong – is the default so you don’t need to type it. This means any object created usingalloc/init is retained for the lifetime of its current scope. The “current scope” usually means the braces in which the variable is declared (i.e. a method, for loop, if block, etc…)
  • __weak – means the object can be destroyed at anytime. This is only useful if the object is somehow strongly referenced somewhere else. When destroyed, a variable with __weak is set tonil.
  • __unsafe_unretained – is just like __weak but the poiner is not set to nil when the object is deallocated. Instead the pointer is left dangling (i.e. it no longer points to anything useful).
  • __autoreleasing, not to be confused with calling autorelease on an object before returning it from a method, this is used for passing objects by reference, for example when passing NSError objects by reference such as [myObject performOperationWithError:&tmp];
Source: LLVM Clang Objective-C Automatic Reference Counting Documentation
NB: We have found @property declarations using ‘retain’ with ARC enabled (i.e. instead of ‘strong’) do not cause compiler warnings and yield the same compiled result. This could change in future so stick to ‘strong’ for clarity.

Migrating Existing Projects to ARC

Xcode 4.2 provides a conversion tool that migrates existing code to ARC, and helps you manually convert code that cannot be automatically migrated.
1. Open your non-ARC compliant project and go to Edit > Refactor > Convert to Objective-C ARC.

2. Select the build targets and contained files to convert (you exclude files in laters steps too)

3. Run Precheck and press next.

NB: When you press next the LLVM compiler will build the project in order to analyse it. If your project has any errors, you cannot proceed to the next step. If you are opening a project from a previous Xcode version for the first time, remember to Clean.
4. Inspect the proposed changes and choose to include/exclude any files. Then press save.

NB: You will also be notified of any files that cannot be migrated. Not all files (including existing libraries) need to be migrated. ARC works on a file by file basis, so see the section below on how to exclude them at compile time.
5. The migration tool will automagically set the compiler flag enabling ARC to on. You can confirm this in your project’s build settings (search for ‘reference counting’).

Including Code that is not ARC Compliant

According to Apple’s documentation: “ARC interoperates with manual reference counting code on a per-file basis. If you want to continue using manual reference counting for some files, you can do so.”
This means some files can use ARC and some files can be spared from it’s magical grasp. Here are the steps for bulk excluding files from ARC at compile time. At the time of writing, many popular libraries are not ARC ready, to get around this follow these steps:
  1. Click on your Project in the Xcode project tree
  2. Click on the Target
  3. Select the Build Phases tab
  4. Expand the Compile Sources section
  5. Select one or more files you want to exclude from ARC
  6. Press the return key
  7. Type -fno-objc-arc
  8. Press the return key again
  9. Each file selected now has a -fno-objc-arc compiler flag set and will be excluded from ARC

Should I Use ARC?

If you are new to Objective-C, you will certainly welcome it. There is more than enough to learn at first without worrying about reference counted memory. Once you start using existing libraries though, you are sure to experience pain until you get accustomed to explicitly excluding them from ARC pre-compilation.
If you’re already coding happily without ARC, you’re probably thinking “I don’t need it!” This may be the right answer for you — for now. Most popular libraries haven’t been converted to ARC and it does not play well with Core Foundation classes. There are specific limitations when using CF classes listed in Apple’s documentation, and a bunch of extra qualifiers are needed to make Toll Free Bridging work when migrating code.
From what I have seen and read, ARC is ready to use right now. However, it is probably best left to new projects until you get familiar with it. Whilst ARC is backwards compatible with iOS 4.0 onwards, weak references are only supported in iOS 5.0 onwards — possibly another reason not to migrate everything just yet (there are work arounds to this, see the “Resources” section at the bottom of this post).
Performance wise, some early reports claim ARC can make your projects faster – possibly due to less reliance on autorelease. However, you can also do that yourself by “coding better” and using retain/release more judiciously. But that’s just the point: ARC should always choose the most optimised approach for your code automatically.
Right now there is some pain involved in moving to ARC, but not a lot. At Long Weekend we will leave it for new projects at first, but this is Apple’s “new” recommended approach, so you can be sure future design decisions will build on ARC and move further away from manual reference counting.

No comments:

Post a Comment