ARC, or why you will love LLVM
Don’t take me wrong, I strongly believe that anyone writing a compiler deserves my respect. Is not a task for the feeblest. Yet, my relationship with GCC was not always a good one. How not to hate the cryptic messages spilled by the parser from time to time? Well, let’s praise Stallman irreducibility, Apple condemn GCC and replace it with LLVM1. Regardless of the actual reasons, the change brings a better compiler to the table. One that can parse and understand code even better than my cocker spaniel, which is much more of what GCC gives us. And because the compiler is able to understand the code, well, to understand it a little better than GCC, then Apple decided that LLVM can take care of the memory-management tasks. In Apple own words:
“The compiler has a complete understanding of your objects, and releases each object the instant it is no longer used, so apps run as fast as ever, with predictable, smooth performance.”
And yes, the best part is that this is not GC. It is just let the compiler insert the release when needed. ARC works on top of the familiar reference counting.
Enabling ARC
Basically, ARC requires Apple LLVM compiler 3.0 (or later, as the time of writing, the default compiler is Apple LLVM 3.1), and the compiler flag -fobjc-arc set. Both of these requirements are set by default by Xcode 4.2 and later.
Ownership
As you must remember, the only secret of reference counting is to keep an eye on ownership.
- You are owner of every object you create (using
+alloc,+allocWithZone:or+new. - You can share ownership of an object (messaging it with
-copy,-mutableCopyor-retain) - When no longer needed, ownership is relinquished with
-release. - Only relinquish objects that you own.
To ease the task, ARC introduced a few qualifiers to instruct the compiler how the objects ownership would be handle.
__strong
Default qualifier.
Semantically,
NSString __strong * someString = [[NSString alloc] initWithString:@"some string"];
Is the same of
NSString * someString = [[NSString alloc] initWithString:@"some string"];
The ownership is relinquish at the end of the present scope. This means that the release message is send automatically when the control flow leaves the scope where the strong variable was obtained.
By the way, any variable qualified with __strong, __weak and __autoreleasing are initialized to nil.
id __strong someStringVar;
id __weak someWeakVar;
id __autoreleasing someAutoreleasingVar;
Is semantically the same of
id __strong someStringVar = nil;
id __weak someWeakVar = nil;
id __autoreleasing someAutoreleasingVar = nil;
This seems small, but add o the security of the code (we are no longer dealing with undefined states).
The strong reference will generate ownership even for objects that are not created.
NSSet __strong *set = [NSSet set];
Here set is owned for the whole life of the scope.
Members variables can be qualified as __strong as well. In fact, because the compiler can insert automatically the members variable, all we need to do is to define the property.
@property (strong) CLLocation *latestKnownLocation;
And obviously, synthesize it.
@synthesize latestKnownLocation;
As long as the class instance is alive, there is going to be a strong reference to latestKnownLocation.
__weak
So, by default (we don’t even have to type __strong), the compiler will take care of the memory management most of the time. But, what happens, for instance, when two objects refer each other. That’s exactly the kind of relationship that exists between an object and its delegate. We have a tableViewDelegate which is going to serve as a delegate for a UITableView. If the table view delegate property is qualified as strong, and the same happens with the tableView property of the delegate, then, when the delegate reference counter is never going to be zero, because there is always a reference from the table view, and the table view reference counter is also impossible to reduce to zero, because it is referenced from the delegate. This is circular reference.
To avoid these kind of situations, the rule of thumb is not to claim ownership of delegates and other objects that refers to each other (like IBOutlets for instance). Let’s revisit our previous example, because the table view does not claim ownership of the delegate, the delegate can be deallocated with no problem, and because there is no delegate referencing the table view, the the table view can also be deallocated. Even better, when the object that a variable qualified as __weak is discarded, the variable value is set to nil.
The __weak qualifier is only available for iOS 5 (or later) and OSX Lion (or later). For older OSs, __unsafe_unretained qualifier must be used instead.
__unsafe_unretained
This qualifier exclude the variable from ARC mechanisms. Because no check is perform, and references are not niled, if you set a variable as __unsafe_unretained you hold the responsibility of checking if the object is still there when needed.
__autoreleasing
Because ARC handles the little nuances of memory management, it is very rare to use auto release mechanism explicitly, thou the environment provides the needed tools.
The thing is that when an object is referenced but not created, i.e., none of the methods in the family +alloc, or -copy is called, the object is automatically registered to the auto release pool. When an object is returned from a method, the compiler checks the name of the method. Naming convention is important here because the compiler used the method name to know how to handle the returned object ownership.
Most programmers are going to use __autoreleasing mainly to handle errors via indirect pointers. In ARC indirect pointers are by default qualified as __autoreleasing. The method that handle the error will take an __autoreleasing parameter:
- (BOOL)performWithError:(__autoreleasing NSError **)error {
// try something prone to failure
if ( failure ) {
*error = [[NSError alloc] initWithDomain:@"Some Error Domain"
code:101
userInfo:nil];
return NO;
}
return YES
}
And the caller portion would be something like
NSError __autoreleasing *error = nil;
if ( ![self performWithError:&error] ) {
// Deal with the error
NSLog(@"error: %@", error);
}
Legacy Code
There are plenty of code that is not ARC ready (yet). That’s not a big deal. Once again, ARC is not GC, and it works on a a copyable unit basis. To add non-ARC units to your app, Select the Target you are working with, then the Build Phases tab, and look for legacy units in Compiled Sources. Add -fno-objc-arc to each one. Recompile. You are done.
-
You would like to think that they choose compiler over the other because one is better, but that’s not the whole history. The tipping point was the more forgiven nature of LLVM License ↩