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 Tutorials. Matthijs Hollemans wrote this chapter – the same guy who wrote the iOS Apprentice Series. Enjoy!
This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer.
The most disruptive change in iOS 5 is the addition of Automatic Reference Counting, or ARC for short. ARC is a feature of the new LLVM 3.0 compiler and it completely does away with the manual memory management that all iOS developers love to hate.
Using ARC in your own projects is extremely simple. You keep programming as usual, except that you no longer call retain, release and autorelease. That’s basically all there is to it.
With Automatic Reference Counting enabled, the compiler will automatically insert retain, release and autorelease in the correct places in your program. You no longer have to worry about any of this, because the compiler does it for you. I call that freaking awesome. In fact, using ARC is so simple that you can stop reading this tutorial now. ;-)
But if you’re still skeptical about ARC — maybe you don’t trust that it will always do the right thing, or you think that it somehow will be slower than doing memory management by yourself — then read on. The rest of this tutorial will dispel those myths and show you how to deal with some of the less intuitive consequences of enabling ARC in your projects.
In addition, we’ll give you hands-on experience with converting an app that doesn’t use ARC at all to using ARC. You can use these same techniques to convert your existing iOS projects to use ARC, saving yourself tons of memory headaches!
How It Works
You’re probably already familiar with manual memory management, which basically works like this:
- If you need to keep an object around you need to retain it, unless it already was retained for you.
- If you want to stop using an object you need to release it, unless it was already released for you (with autorelease).
As a beginner you may have had a hard time wrapping your head around the concept but after a while it became second nature and now you’ll always properly balance your retains with your releases. Except when you forget.
The principles of manual memory management aren’t hard but it’s very easy to make a mistake. And these small mistakes can have dire consequences. Either your app will crash at some point because you’ve released an object too often and your variables are pointing at data that is no longer valid, or you’ll run out of memory because you don’t release objects enough and they stick around forever.
The static analyzer from Xcode is a great help in finding these kinds of problems but ARC goes a step further. It avoids memory management problems completely by automatically inserting the proper retains and releases for you!
It is important to realize that ARC is a feature of the Objective-C compiler and therefore all the ARC stuff happens when you build your app. ARC is not a runtime feature (except for one small part, the weak pointer system), nor is it *garbage collection* that you may know from other languages.
All that ARC does is insert retains and releases into your code when it compiles it, exactly where you would have — or at least should have — put them yourself. That makes ARC just as fast as manually managed code, and sometimes even a bit faster because it can perform certain optimizations under the hood.
Pointers Keep Objects Alive
The new rules you have to learn for ARC are quite simple. With manual memory management you needed to retain an object to keep it alive. That is no longer necessary, all you have to do is make a pointer to the object. As long as there is a variable pointing to an object, that object stays in memory. When the pointer gets a new value or ceases to exist, the associated object is released. This is true for all variables: instance variables, synthesized properties, and even local variables.
It makes sense to think of this in terms of ownership. When you do the following,
NSString *firstName = self.textField.text; |
the firstName variable becomes a pointer to the NSString object that holds the contents of text field. That firstName variable is now the owner of that string object.
An object can have more than one owner. Until the user changes the contents of the UITextField, its text property is also an owner of the string object. There are two pointers keeping that same string object alive:
Moments later the user will type something new into the text field and its text property now points at a new string object. But the original string object still has an owner (the firstName variable) and therefore stays in memory.
Only when firstName gets a new value too, or goes out of scope — because it’s a local variable and the method ends, or because it’s an instance variable and the object it belongs to is deallocated — does the ownership expire. The string object no longer has any owners, its retain count drops to 0 and the object is deallocated.
We call pointers such as firstName and textField.text “strong” because they keep objects alive. By default all instance variables and local variables are strong pointers.
There is also a “weak” pointer. Variables that are weak can still point to objects but they do not become owners:
__weak NSString *weakName = self.textField.text; |
The weakName variable points at the same string object that the textField.text property points to, but is not an owner. If the text field contents change, then the string object no longer has any owners and is deallocated:
When this happens, the value of weakName automatically becomes nil. It is said to be a “zeroing” weak pointer.
Note that this is extremely convenient because it prevents weak pointers from pointing to deallocated memory. This sort of thing used to cause a lot of bugs — you may have heard of the term “dangling pointers” or “zombies” — but thanks to these zeroing weak pointers that is no longer an issue!
You probably won’t use weak pointers very much. They are mostly useful when two objects have a parent-child relationship. The parent will have a strong pointer to the child — and therefore “owns” the child — but in order to prevent ownership cycles, the child only has a weak pointer back to the parent.
An example of this is the delegate pattern. Your view controller may own a UITableView through a strong pointer. The table view’s data source and delegate pointers point back at the view controller, but are weak. We’ll talk more about this later.
Note that the following isn’t very useful:
__weak NSString *str = [[NSString alloc] initWithFormat:...]; NSLog(@"%@", str); // will output "(null)" |
There is no owner for the string object (because str is weak) and the object will be deallocated immediately after it is created. Xcode will give a warning when you do this because it’s probably not what you intended to happen (“Warning: assigning retained object to weak variable; object will be released after assignment”).
You can use the __strong keyword to signify that a variable is a strong pointer:
__strong NSString *firstName = self.textField.text; |
But because variables are strong by default this is a bit superfluous.
Properties can also be strong and weak. The notation for properties is:
@property (nonatomic, strong) NSString *firstName; @property (nonatomic, weak) id <MyDelegate> delegate; |
ARC is great and will really remove a lot of clutter from your code. You no longer have to think about when to retain and when to release, just about how your objects relate to each other. The question that you’ll be asking yourself is: who owns what?
For example, it was impossible to write code like this before:
id obj = [array objectAtIndex:0]; [array removeObjectAtIndex:0]; NSLog(@"%@", obj); |
Under manual memory management, removing the object from the array would invalidate the contents of the obj variable. The object got deallocated as soon as it no longer was part of the array. Printing the object with NSLog() would likely crash your app. On ARC the above code works as intended. Because we put the object into the obj variable, which is a strong pointer, the array is no longer the only owner of the object. Even if we remove the object from the array, the object is still alive because obj keeps pointing at it.
Automatic Reference Counting also has a few limitations. For starters, ARC only works on Objective-C objects. If your app uses Core Foundation or malloc() and free(), then you’re still responsible for doing the memory management there. We’ll see examples of this later in the tutorial. In addition, certain language rules have been made stricter in order to make sure ARC can always do its job properly. These are only small sacrifices, you gain a lot more than you give up!
Just because ARC takes care of doing retain and release for you in the proper places, doesn’t mean you can completely forget about memory management altogether. Because strong pointers keep objects alive, there are still situations where you will need to set these pointers to nil by hand, or your app might run out of available memory. If you keep holding on to all the objects you’ve ever created, then ARC will never be able to release them. Therefore, whenever you create a new object, you still need to think about who owns it and how long the object should stay in existence.
There is no doubt about it, ARC is the future of Objective-C. Apple encourages developers to turn their backs on manual memory management and to start writing their new apps using ARC. It makes for simpler source code and more robust apps. With ARC, memory-related crashes are a thing of the past.
But because we’re entering a transitioning period from manual to automatic memory management you’ll often come across code that isn’t compatible with ARC yet, whether it’s your own code or third-party libraries. Fortunately you can combine ARC with non-ARC code in the same project and I’ll show you several ways how.
ARC even combines well with C++. With a few restrictions you can also use ARC on iOS 4, which should only help to speed up the adoption.
A smart developer tries to automate as much of his job as possible, and that’s exactly what ARC offers: automation of menial programming work that you had to do by hand previously. To me, switching is a no-brainer.
The App
To illustrate how to use Automatic Reference Counting in practice, I have prepared a simple app that we are going to convert from manual memory management to ARC. The app, Artists, consists of a single screen with a table view and a search bar. When you type something into the search bar, the app employs the MusicBrainz API to search for musicians with matching names.
The app looks like this:
In their own words, MusicBrainz is “an open music encyclopedia that collects, and makes available to the public, music metadata”. They have a free XML web service that you can use from your own apps. To learn more about MusicBrainz, check out their website at http://musicbrainz.org.
Go ahead and download the starter project for this tutorial and open it in Xcode. The project contains the following source files:
- AppDelegate.h/.m: The application delegate. Nothing special here, every app has one. It loads the view controller and puts it into the window.
- MainViewController.h/.m/.xib: The view controller for the app. It has a table view and a search bar, and does most of the work.
- SoundEffect.h/.m: A simple class for playing sound effects. The app will make a little beep when the MusicBrainz search is completed.
- main.m: The entry point for the app.
In addition, the app uses two third-party libraries. Your apps probably use a few external components of their own and it’s good to learn how to make these libraries play nice with ARC.
- AFHTTPRequestOperation.h/.m: Part of the AFNetworking library that makes it easy to perform requests to web services. I did not include the full library because we only need this one class. You can find the complete package at: https://github.com/gowalla/AFNetworking
- SVProgresHUD.h/.m/.bundle: A progress indicator that we will display on the screen while the search is taking place. You may not have seen .bundle files before. This is a special type of folder that contains the image files that are used by SVProgressHUD. To view these images, right-click the .bundle file in Finder and choose the Show Package Contents menu option. For more info about this component, see: https://github.com/samvermette/SVProgressHUD
Let’s quickly go through the code for the view controller so you have a decent idea of how the app works. MainViewController is a subclass of UIViewController. Its nib file contains a UITableView object and a UISearchBar object:
The table view displays the contents of the searchResults array. Initially this pointer is nil. When the user performs a search, we fill up the array with the response from the MusicBrainz server. If there were no search results, the array is empty (but not nil) and the table says: “(Nothing found)”. This all takes place in the usual UITableViewDataSource methods: numberOfRowsInSection and cellForRowAtIndexPath.
The actual search is initiated from the searchBarSearchButtonClicked method, which is part of the UISearchBarDelegate protocol.
- (void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar { [SVProgressHUD showInView:self.view status:nil networkIndicator:YES posY:-1 maskType:SVProgressHUDMaskTypeGradient]; |
First, we create a new HUD and show it on top of the table view and search bar, blocking any user input until the network request is done:
Then we create the URL for the HTTP request. We use the MusicBrainz API to search for artists.
NSString *urlString = [NSString stringWithFormat: @"http://musicbrainz.org/ws/2/artist?query=artist:%@&limit=20", [self escape:searchBar.text]]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]]; |
The search text is URL-encoded using the escape: method to ensure that we’re making a valid URL. Spaces and other special characters are turned into things such as %20.
NSDictionary *headers = [NSDictionary dictionaryWithObject: [self userAgent] forKey:@"User-Agent"]; [request setAllHTTPHeaderFields:headers]; |
We add a custom User-Agent header to the HTTP request. The MusicBrainz API requires this. All requests should “have a proper User-Agent header that identifies the application and the version of the application making the request.” It’s always a good idea to play nice with the APIs you’re using, so we construct a User-Agent header that looks like:
com.yourcompany.Artists/1.0 (unknown, iPhone OS 5.0, iPhone Simulator, Scale/1.000000) |
(I took this formula from another part of the AFNetworking library and put it into the userAgent method in the view controller.)
The MusicBrainz API has a few other restrictions too. Client applications must not make more than one web service call per second or they risk getting their IP address blocked. That won’t be too big of an issue for our app — it’s unlikely that a user will be doing that many searches — so we take no particular precautions for this.
Once we have constructed the NSMutableURLRequest object, we give it to AFHTTPRequestOperation to perform:
AFHTTPRequestOperation *operation = [AFHTTPRequestOperation operationWithRequest:request completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) { // ... }]; [queue addOperation:operation]; |
AFHTTPRequestOperation is a subclass of NSOperation, which means we can add it to an NSOperationQueue (in the queue variable) and it will be handled asynchronously. Because of the HUD, the app ignores any user input while the request is taking place.
We give AFHTTPRequestOperation a block that it invokes when the request completes. Inside the block we first check whether the request was successful (HTTP status code 200) or not. For this app we’re not particularly interested in why a request failed; if it does we simply tell the HUD to dismiss with a special “error” animation. Note that the completion block is not necessarily executed on the main thread and therefore we need to wrap the call to SVProgressHUD in dispatch_async().
if (response.statusCode == 200 && data != nil) { . . . } else // something went wrong { dispatch_async(dispatch_get_main_queue(), ^ { [SVProgressHUD dismissWithError:@"Error"]; }); } |
Now for the interesting part. If the request succeeds, we allocate the searchResults array and parse the response. The response is XML so we use NSXMLParser to do the job.
self.searchResults = [NSMutableArray arrayWithCapacity:10]; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; [parser setDelegate:self]; [parser parse]; [parser release]; [self.searchResults sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; |
You can look up the logic for the XML parsing in the NSXMLParserDelegate methods, but essentially we just look for elements named “sort-name”. These contain the names of the artists. We add those names as NSString objects to the searchResults array. When the XML parser is done, we sort the results alphabetically, and then update the screen on the main thread:
dispatch_async(dispatch_get_main_queue(), ^ { [self.soundEffect play]; [self.tableView reloadData]; [SVProgressHUD dismiss]; }); |
That’s it for how the app works. It’s written using manual memory management and doesn’t use any iOS 5 specific features. Now let’s convert it to ARC.
Automatic Conversion
We are going to convert the Artists app to ARC. Basically this means we’ll just get rid of all the calls to retain, release, and autorelease, but we’ll also run into a few situations that require special attention.
There are three things you can do to make your app ARC-compatible:
- Xcode has an automatic conversion tool that can migrate your source files.
- You can convert the files by hand.
- You can disable ARC for source files that you do not want to convert. This is useful for third-party libraries that you don’t feel like messing with.
We will use all of these options on the Artists app, just to show you how it all works. In this section, we are going to convert the source files with Xcode’s automated conversion tool, except for MainViewController and AFHTTPRequestOperation.
Before we do that, you should make a copy of the project as the tool will overwrite the original files. Xcode does offer to make a snapshot of the source files but just as a precaution I would make a backup anyway.
ARC is a feature of the new LLVM 3.0 compiler. Your existing projects most likely use the older GCC 4.2 or LLVM-GCC compilers, so it’s a good idea to switch the project to the new compiler first and see if it compiles cleanly in non-ARC mode. Go to the Project Settings screen, select the Artists target and under Build Settings type “compiler” into the search box. This will filter the list to bring up just the compiler options:
Click on the Compiler for C/C++/Objective-C option to change it to Apple LLVM compiler 3.0:
Under the Warnings header, also set the Other Linker Flags option to -Wall. The compiler will now check for all possible situations that can cause problems. By default many of these warnings are turned off but I find it useful to always have all warnings on and to treat them as fatal errors. In other words, if the compiler gives a warning I will first fix it before continuing. Whether that is something you may want to do on your own projects is up to you, but during the conversion to ARC I recommend that you take a good look at any issues the compiler may complain about.
For the exact same reason, also enable the Run Static Analyzer option under the Build Options header:
Xcode will now run the analyzer every time we build the app. That makes the builds a bit slower but for an app of this size that’s barely noticeable.
Let’s build the app to see if it gives any problems with the new compiler. First do a clean using the Product -> Clean menu option (or Shift-Cmd-K). Then press Cmd-B to build the app. Xcode should give no errors or warnings, so that’s cool. If you’re converting your own app to ARC and you get any warning messages at this point, then now is the time to fix them.
Just for the fun of it, let’s switch the compiler to ARC mode and make it build the app again. We’re going to get a ton of error messages but it’s instructive to see what exactly these are.
Still in the Build Settings screen, switch to “All” to see all the available settings (instead of Basic, which only shows the most-often used settings). Search for “automatic” and set the Objective-C Automatic Reference Counting option to Yes. This is a project-wide flag that tells Xcode that you wish to compile all of the source files in your project using the ARC compiler.
Build the app again. Whoops, you should get a ton of errors:
Clearly we have some migrating to do! Most of these errors are pretty obvious, they say you can no longer use retain, release and autorelease. We could fix all of these errors by hand but it’s much easier to employ the automatic conversion tool. The tool will compile the app in ARC mode and rewrites the source code for every error it encounters, until it compiles cleanly.
From Xcode’s menu, choose Edit\Refactor\Convert to Objective-C ARC.
A new window appears that lets you select which parts of the app you want to convert:
For the purposes of this tutorial, we don’t want to do the whole app, so select only the following files:
- main.m
- AppDelegate.m
- SVProgressHUD.m
- SoundEffect.m
The dialog shows a little warning icon indicating that the project already uses ARC. That’s because we’ve enabled the Objective-C Automatic Reference Counting option in the Build Settings earlier and now the conversion tool thinks this is already an ARC project. You can ignore the warning, it won’t interfere with the conversion.
Press the Precheck button to begin. The tool first checks whether your code is in a good enough state to be converted to ARC. We did manage to build our app successfully with the new LLVM 3.0 compiler, but apparently that wasn’t good enough. Xcode gives the following error message:
It complains about “ARC readiness issues” and that we should enable the “Continue building after errors” option. We’ll do the latter first. Open the Xcode Preferences window (from the menubar, under Xcode) and go to the General tab. Enable the option Continue building after errors:
Let’s try again. Choose Edit\Refactor\Convert to Objective-C ARC and select all the source files except for MainViewController.m and AFHTTPRequestOperation.m. Press Precheck to begin.
No luck, again we get an error message. The difference with before is that this time the compiler was able to identify all the issues we need to fix before we can do the conversion. Fortunately, there is only one:
(You may have more errors in this list than are displayed here. Sometimes the conversion tool also complains about things that are not really “ARC readiness” issues.)
The full description of our issue is:
Cast of Objective-C pointer type 'NSURL *' to C pointer type 'CFURLRef' (aka 'const struct __CFURL *') requires a bridged cast |
What it looks like in the source editor:
I will go into more detail about this later, but the source code here attempts to cast an NSURL object to a CFURLRef object. The AudioServicesCreateSystemSoundID() function takes a CFURLRef that describes where the sound file is located, but we’re giving it an NSURL object instead. CFURLRef and NSURL are “toll-free bridged”, which makes it possible to use an NSURL object in place of a CFURLRef and vice versa.
Often the C-based APIs from iOS will use Core Foundation objects (that’s what the CF stands for) while the Objective-C based APIs use “true” objects that extend the NSObject class. Sometimes you need to convert between the two and that is what the toll-free bridging technique allows for.
However, when you use ARC the compiler needs to know what it should do with those toll-free bridged objects. If you use an NSURL in place of a CFURLRef, then who is responsible for releasing that memory at the end of the day? To solve this conundrum, a set of new keywords was introduced: __bridge, __bridge_transfer and __bridge_retained. We will go into more depth on how to use these later in the tutorial.
For now, we need to change the source code to the following:
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef) fileURL, &theSoundID); |
The pre-check may have given you errors other than just this one. You can safely ignore those, the above change in SoundEffect.m is the only one we need to make. The conversion tool seems a little uncertain in what it considers an “ARC readiness issue” from time to time.
Let’s run the conversion tool once more – Edit\Refactor\Convert to Objective-C ARC. This time the pre-check runs without problems and we’re presented with the following screen:
Click Next to continue. After a few seconds, Xcode will show a preview of all the files that it will change and which changes it will make. The left-hand pane shows the changed files while the right-hand pane shows the originals.
It’s always a good idea to step through these files to make sure Xcode doesn’t mess up anything. Let’s go through the changes that the conversion tool is proposing to make.
AppDelegate.h
@property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) MainViewController *viewController; |
The app delegate has two properties, one for the window and one for the main view controller. This particular project does not use a MainWindow.xib file, so these two objects are created by the AppDelegate itself in application:didFinishLaunchingWithOptions: and stored into properties in order to simplify memory management.
These property declarations change from this,
@property (retain, nonatomic) |
to this:
@property (strong, nonatomic) |
The strong keyword means what you think it does. It tells ARC that the synthesized ivar that backs this property holds a strong reference to the object in question. In other words, the window property contains a pointer to a UIWindow object and also acts as the owner of that UIWindow object. As long as the window property keeps its value, the UIWindow object stays alive. The same thing goes for the viewController property and the MainViewController object.
AppDelegate.m
In AppDelegate.m the lines that create the window and view controller objects have changed and the dealloc method is removed completely:
Spot the differences between this,
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; |
and this:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; |
That’s correct, the call to autorelease is no longer needed. Likewise for the line that creates the view controller.
self.viewController = [[[MainViewController alloc] initWithNibName: @"MainViewController" bundle:nil] autorelease]; |
now becomes:
self.viewController = [[MainViewController alloc] initWithNibName: @"MainViewController" bundle:nil]; |
Before ARC, if you wrote the following you created a memory leak if the property was declared “retain”:
self.someProperty = [[SomeClass alloc] init]; |
The init method returns a retained object and placing it into the property would retain the object again. That’s why you had to use autorelease, to balance the retain from the init method. But with ARC the above is just fine. The compiler is smart enough to figure out that it shouldn’t do two retains here.
One of the things I love about ARC is that in most cases it completely does away with the need to write dealloc methods. When an object is deallocated its instance variables and synthesized properties are automatically released. You no longer have to write:
- (void)dealloc { [_window release]; [_viewController release]; [super dealloc]; } |
because Objective-C automatically takes care of this now. In fact, it’s not even possible to write the above anymore. Under ARC you are not allowed to call release, nor [super dealloc]. You can still implement dealloc — and you’ll see an example of this later — but it’s no longer necessary to release your ivars by hand.
Something that the conversion tool doesn’t do is making AppDelegate a subclass of UIResponder instead of NSObject. When you create a new app using one of Xcode’s templates, the AppDelegate class is now a subclass of UIResponder. It doesn’t seem to do any harm to leave it as NSObject, but you can make it a UIResponder if you want to:
@interface AppDelegate : UIResponder <UIApplicationDelegate> |
Main.m
In manually memory managed apps, the [autorelease] method works closely together with an “autorelease pool”, which is represented by an NSAutoreleasePool object. Every main.m has one and if you’ve ever worked with threads directly you’ve had to make your own NSAutoreleasePool for each thread. Sometimes developers also put their own NSAutoreleasePools inside loops that do a lot of processing, just to make sure autoreleased objects created in that loop don’t take up too much memory and get deleted from time to time.
Autorelease didn’t go away with ARC, even though you never directly call the [autorelease] method on objects anymore. Any time you return an object from a method whose name doesn’t start with alloc, init, copy, mutableCopy or new, the ARC compiler will autorelease it for you. These objects still end up in an autorelease pool. The big difference with before is that the NSAutoreleasePool has been retired in favor of a new language construct, @autoreleasepool.
The conversion tool turned our main() function from this,
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); [pool release]; return retVal; |
into this:
@autoreleasepool { int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); return retVal; } |
Not only is it simpler to read for us programmers but under the hood a lot has changed as well, making these new autorelease pools a lot faster than before. You hardly ever need to worry about autorelease with ARC, except that if you used NSAutoreleasePool in your code before, you will need to replace it with an @autoreleasepool block. The conversion tool should do that automatically for you as it did here.
SoundEffect.m
Not much changed in this file, only the call to [super dealloc] was removed. You are no longer allowed to call super in your dealloc methods.
Notice that a dealloc method is still necessary here. In most of your classes you can simply forget about dealloc and let the compiler take care of it. Sometimes, however, you will need to release resources manually. That’s the case with this class as well. When the SoundEffect object is deallocated, we still need to call AudioServicesDisposeSystemSoundID() to clean up the sound object and dealloc is the perfect place for that.
SVProgressHUD.m
This file has the most changes of them all but again they’re quite trivial.
At the top of SVProgressHUD.m you’ll find a so-called “class extension”, @interface SVProgressHUD (), that has several property declarations. If you’re unfamiliar with class extensions, they are like categories except they have special powers. The declaration of a class extension looks like that of a category but it has no name between the () parentheses. Class extensions can have properties and instance variables, something that categories can’t, but you can only use them inside your .m file. (In other words, you can’t use a class extension on someone else’s class.)
The cool thing about class extensions is that they allow you to add private properties and method names to your classes. If you don’t want to expose certain properties or methods in your public @interface, then you can put them in a class extension. That’s exactly what the author of SVProgressHUD did.
@interface SVProgressHUD () ... @property (nonatomic, strong) NSTimer *fadeOutTimer; @property (nonatomic, strong) UILabel *stringLabel; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIActivityIndicatorView *spinnerView; ... @end |
As we’ve seen before, retain properties will become strong properties. If you scroll through the preview window you’ll see that all the other changes are simply removal of retain and release statements.
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:
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:
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:
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.
Where To Go From Here?
Stay tuned for the second part in the series, where we’ll cover converting files to ARC by hand, ARC as it relates to Core Foundation, weak properties, and much more!
If you have any questions or comments on this tutorial or ARC in iOS 5 in general, please join the forum discussion below!
This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer.
Beginning ARC in iOS 5 Tutorial Part 1 is a post from: Ray Wenderlich
没有评论:
发表评论