Beginning ARC in iOS 5 Tutorial Part 1

Update 10/24/12: If you’d like a new version of this tutorial fully updated for iOS 6 and Xcode 4.5, check out iOS 5 by Tutorials Second Edition! Note from Ray: This is the twelfth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 5 of 6 of this article. Click here to view the first page.

Doing It For Real

When you’re satisfied with the changes that the conversion tool will make, press the Save button to make it happen. Xcode first asks whether you want it to take a snapshot of the project before it changes the files:

descr

You should probably press Enable here. If you ever need to go back to the original code, you can find the snapshot in the Organizer window under Projects.

After the ARC conversion tool finishes, press Cmd+B to build the app. The build should complete successfully, but there will be several new warnings in SVProgressHUD.m:

descr

Notice again that the dealloc method is still used in this class, in this case to stop a timer and to unregister for notifications from NSNotificationCenter. Obviously, these are not things ARC will do for you.

The code at the line with the warning used to look like this:

if(fadeOutTimer != nil)
	[fadeOutTimer invalidate], [fadeOutTimer release], fadeOutTimer = nil;

Now it says:

if(fadeOutTimer != nil)
	[fadeOutTimer invalidate], fadeOutTimer, fadeOutTimer = nil;

The tool did remove the call to [release], but it left the variable in place. A variable all by itself doesn’t do anything useful, hence the Xcode warning. This appears to be a situation the automated conversion tool did not foresee.

If you’re confused by the commas, then know that it is valid in Objective-C to combine multiple expressions into a single statement using commas. The above trick is a common idiom for releasing objects and setting the corresponding ivar to nil. Because everything happens in a single statement, the if doesn’t need curly braces.

To silence the warning you can change this line, and the others like it, to:

if(fadeOutTimer != nil)
    [fadeOutTimer invalidate], fadeOutTimer = nil;

Technically speaking we don’t need to do fadeOutTimer = nil; in dealloc, because the object will automatically release any instance variables when it gets deleted. In the other methods where the timer is invalidated, however, you definitely should set fadeOutTimer to nil. If you don’t, the SVProgressHUD keeps hanging on to the invalidated NSTimer object longer than it’s supposed to.

Build the app again and now there should be no warnings. Conversion complete!

But wait a minute… we skipped MainViewController and AFHTTPRequestOperation when we did the conversion. How come they suddenly compile without problems? When we tried building the project with ARC enabled earlier there were certainly plenty of errors in those files.

The answer is simple: the conversion tool has disabled ARC for these two source files. You can see that in the Build Phases tab on the Project Settings screen:

descr

We enabled ARC on a project-wide basis earlier when we changed the Objective-C Automatic Reference Counting setting under Build Settings to Yes. But you can make exceptions to this by telling the compiler to ignore ARC for specific files, using the -fno-objc-arc flag. Xcode will compile these files with ARC disabled.

Because it’s unreasonable to expect developers to switch their entire projects to ARC at once, the folks at Apple made it possible to combine ARC code with non-ARC code in the same project. Tip: An easy way to do that is to simply convert the files that you want to migrate with the conversion tool, and let it automatically add the -fno-objc-arc flag to the rest. You could also add this flag by hand but that’s incredibly annoying when you have many files that you don’t want to ARCify.

Migration Woes

Our conversion went fairly smooth. We only had to make a single change to SoundEffect.m (inserting a __bridge) statement and then the tool did the rest.
However, the LLVM 3.0 compiler is a little less forgiving in ARC mode than previous compilers so it is possible that you run into additional problems during the pre-check. You may have to edit your own code more extensively before the tool can take over.

Here’s a handy reference of some issues you might run into, and some tips for how to resolve them:

“Cast … requires a bridged cast”

This is the one we’ve seen before. When the compiler can’t figure out by itself how to do the cast, it expects you to insert a __bridge modifier. There are two other bridge types, __bridge_transfer and __bridge_retained, and which one you’re supposed to use depends on exactly what you’re trying to do. More about these other bridge types in the section on Toll-Free Bridging.

“Receiver type ‘X’ for instance message is a forward declaration”

If you have a class, let’s say MyView that is a subclass of UIView, and you call a method on it or use one of its properties, then you have to #import the definition for that class. That is usually a requirement for getting your code to compile in the first place, but not always.

For example, you added a forward declaration in your .h file to announce that MyView is class:

@class MyView;

Later in your .m file you do something like:

[myView setNeedsDisplay];

Previously this might have compiled and worked just fine, even without an #import statement. With ARC you always need to explicitly add an import:

#import "MyView.h"

“Switch case is in protected scope”

You get this error when your code does the following:

switch (X)
{	
    case Y:
        NSString *s = ...;
        break;
}

This is no longer allowed. If you declare new pointer variables inside a case statement you must enclose the whole thing in curlies:

switch (X)
{	
    case Y:
    {
        NSString *s = ...;
        break;
    }
}

Now it is clear what the scope of the variable is, which ARC needs to know in order to release the object at the right moment.

“A name is referenced outside the NSAutoreleasePool scope that it was declared in”

You may have some code that creates its own autorelease pool:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

// . . . do calculations . . .

NSArray* sortedResults = 
  [[filteredResults sortedArrayUsingSelector:@selector(compare:)] 
    retain];

[pool release];
return [sortedResults autorelease];

The conversion tool needs to turn that into something like this:

@autoreleasepool
{
  // . . . do calculations . . .       
  NSArray* sortedResults = [filteredResults sortedArrayUsingSelector:@
    selector(compare:)];	
}
return sortedResults;

But that is no longer valid code. The sortedResults variable is declared inside the @autoreleasepool scope and is therefore not accessible outside of that scope. To fix the issue you will need to move the declaration of the variable above the creation of the NSAutoreleasePool:

NSArray* sortedResults;
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
. . .

Now the conversion tool can properly rewrite your code.

“ARC forbids Objective-C objects in structs or unions”

One of the restrictions of ARC is that you can no longer put Objective-C objects inside C structs. The following code is no longer valid:

typedef struct
{
    UIImage *selectedImage;
    UIImage *disabledImage;
}
ButtonImages;

You are recommended to replace such structs with true Objective-C classes instead. We’ll talk more about this one later, and then I’ll show you some other workarounds.

There may be other pre-check errors but these are the most common ones.

Note: The automated conversion tool can be a little flakey if you use it more than once. If you don’t convert all the files but leave some unchecked the way we did, the conversion tool may not actually do anything when you try to convert the remaining files later. My suggestion is that you run the tool just once and don’t convert your files in batches.

Contributors

Over 300 content creators. Join our team.