Automatic Reference Counting (ARC) in iOS (Part 2)

iOS SDKIn my last blog post, I wrote about ARC and discussed in detailed the __strong and __weak qualifiers, which should cover 80% or more of the use cases out there. In this article, I am going to complete the series by discussing the other two ARC ownership qualifiers __unsafe_unretained and __autoreleasing.

The __unsafe_unretained Qualifier

Variables qualified with __unsafe_unretained are telling the compiler that they do not want to participate in ARC at all. Hence the programmer is responsible for allocating/releasing memory and for handling object lifetimes.

Qualifiers __unsafe_unretained and __weak are actually similar in function. They both claim no ownership of any object that the variables are assigned to (hence “__unretained”) but merely act as a reference the objects. The key difference is that while a __weak variable is assigned to nil after the referenced object is released, a __unsafe_unretained variable to the same object still points to the memory space that was allocated to the object. Because of this, you can’t safely infer if an object has been of disposed with __unsafe_unretained (hence “__unsafe”).

One scenario where you would use __unsafe_unretained is when you declare a data member in a C-struct or union as an Objective-C object like NSString. See below:

struct MyStruct {
  NSString *text; // Won't compile.
}

You will get an error when you compile the code above. Under ARC, NSObject types can’t be members of a C-struct. This is because the compiler can’t manage the lifetime of a C-struct as it can’t determine the lifetime of a struct member. Therefore the developer must manage ┬áthe ownership of the Objective-C object manually (usually through CFRetain and CFRelease). Read here for a full explanation on the use of __unsafe_unretained in C-struct and the compiler error “ARC forbids Objective-C objects in structs or unions.”

struct MyStruct {
  NSString __unsafe_unretained *text; // Now it compiles.
};

The __autoreleasing Qualifier

From Apple document Transitioning to ARC Release Notes: “__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.”

The __autoreleasing qualifier is used to track objects created outside the scope of the caller but still be “retained” so that the object can be accessed by the caller. __autoreleasing is typically used in a method (eg. see method doSomething: below) that returns a BOOL to indicate if the method call is successful or not. If the method fails, we can then access the NSError object for details of the failure. The NSError object is created in the method and returned as an __autoreleasing object to the caller.

@interface MyClass : NSObject
- (BOOL)doSomething:(NSError * __autoreleasing *)myError;
@end

// ...

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    MyClass *obj = [[MyClass alloc] init];

    NSError * __autoreleasing error = nil;
    NSError * __autoreleasing * ptrToError = &error;

    [obj doSomething:ptrToError];

    // This following works as well. I used a more complex approach
    // above to illustrate the intricacies of __autoreleasing.
    [obj doSomething:&error];
  }
}

How should we implement doSomething:? If the following won’t work.

- (BOOL) doSomething:(NSError * __autoreleasing *)myError {
  NSError *error = [[NSError alloc] init];
  myError = &error;

  // ...

  return NO;
}

The problem is that the object variable error is declared implicitly as __strong. And when error varialbe goes out of scope after the control flow leaves doSomething, the error object will be disposed. This won’t work if we want to retain error so that it can be passed back to the caller. To make it work, qualify error with __autoreleasing.

NSError __autoreleasing *error = [[NSError alloc] init];
myError = &error;

Or simply do the following:

- (BOOL) doSomething:(NSError * __autoreleasing *)myError {
  *myError = [[NSError alloc] init];

  // ...

  return NO;
}

By qualifying the parameter myError as __autoreleasing, we ensure that the NSError object created in doSomething is assigned to the autorelease pool and can be safely assigned to an object variable when the control flow is returned back to the caller.

Last but not least, note that all id * is implicitly qualified with __autoreleasing.

Reference and Further Reading