转自:http://www.raywenderlich.com/5773/beginning-arc-in-ios-5-tutorial-part-2
This is the second part of a two part tutorial series on using ARC in iOS 5.
In the first part of the series, we covered how ARC works and how to convert projects to use ARC with Xcode’s built in conversion utility.
In this second and final part of the series, we’ll cover how to convert files by hand, ARC as it relates to Core Foundation, weak properties, and much more!
Converting By Hand
We’ve converted almost the entire project to ARC already, except for MainViewController and AFHTTPRequestOperation. In this section I’ll show you how to convert MainViewController by hand. Sometimes it’s fun to do things yourself so you get a better feel for what truly happens.
If you look at MainViewController.h you’ll see that the class declares two instance variables:
@interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, NSXMLParserDelegate> { NSOperationQueue *queue; NSMutableString *currentStringValue; } |
When you think about it, the public interface of a class is a strange place to put instance variables. Usually, instance variables really are a part of the internals of your class and not something you want to expose in its public interface. To a user of your class it isn’t very important to know what the class’s instance variables are. From the perspective of data hiding it is better if we move such implementation details into the @implementation section of the class. I’m happy to say that this is now possible with LLVM 3.0 (whether you use ARC or not).
Remove the instance variable block from MainViewController.h and put it into MainViewController.m. The header file should now looks like:
#import <UIKit/UIKit.h> @interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, NSXMLParserDelegate> @property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UISearchBar *searchBar; @end |
And the top of MainViewController.m looks like:
@implementation MainViewController { NSOperationQueue *queue; NSMutableString *currentStringValue; } |
Build the app and… it just works. This makes your .h files a lot cleaner and puts the ivars where they really belong.
You can do the same for the SoundEffect class. Simply move the instance variable section into the .m file. Because we now don’t reference the SystemSoundID symbol anywhere in SoundEffect.h, you can also move the #import for AudioServices.h into SoundEffect.m. The SoundEffect header no longer exposes any details of its implementation. Nice and clean.
Note: You can also put instance variables in class extensions. This is useful for when the implementation of your class is spread over multiple files. You can then put the extension in a shared, private header file, so that all these different implementation files have access to the instance variables.
It’s time to enable ARC on MainViewController.m. Go into the Build Phases settings and remove the -fno-objc-arc compiler flag from MainViewController.m. You may have some problems getting Xcode to recognize this. Try doing a new build. You should get a ton of errors but if Xcode still says “Build Succeeded”, then close the project and reopen it.
Dealloc
Let’s go through these errors and fix them one by one. We begin with dealloc:
Every single line in dealloc gives an error. We’re not supposed to call [release] anymore, nor [super dealloc]. Because we’re not doing anything else in dealloc, we can simply remove the entire method.
The only reason for keeping a dealloc method around is when you need to free certain resources that do not fall under ARC’s umbrella. Examples of this are calling CFRelease() on Core Foundation objects, calling free() on memory that you allocated with malloc(), unregistering for notifications, invalidating a timer, and so on.
Sometimes it is necessary to explicitly break a connection with an object if you are its delegate but usually this happens automatically. Most of the time delegates are weak references (something we will get into soon) so when the object to be deallocated is someone else’s delegate, the delegate pointer will be set to nil automatically when the object is destroyed. Weak pointers clean up after themselves.
By the way, in your dealloc method you can still use your instance variables because they haven’t been released yet at that point. That doesn’t happen until after dealloc returns.
The SoundEffect Getter
The soundEffect method calls release, so that’s an easy fix:
This method is actually the getter method for the soundEffect property. It employs a lazy loading technique to load the sound effect the first time it is used. I used a common pattern here for creating objects under manual memory management. First the new object is stored in a temporary local variable, then it is assigned to the actual property, and finally the value from the local variable is released. This is how I used to write this sort of thing and you may have been doing it too:
SoundEffect *theSoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; self.soundEffect = theSoundEffect; [theSoundEffect release]; |
We could just remove the call to release and leave it at that, but now having a separate local variable isn’t very useful anymore:
SoundEffect *theSoundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; self.soundEffect = theSoundEffect; |
So instead, we can simplify it to just one line:
self.soundEffect = [[SoundEffect alloc] initWithSoundNamed: @"Sound.caf"]; |
Under manual memory management this would cause a leak (there is one retain too many going on) but with ARC this sort of thing is just fine.
Please Release Me, Let Me Go
Just like you can’t call release anymore, you also cannot call autorelease:
The fix is straightforward. Instead of doing,
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; |
This line becomes:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; |
The next method that has errors is escape:, but we’ll skip it just for a second. These issues are related to toll-free bridging, a topic that I dedicate a special section to.
The remaining two errors are releases, in searchBarSearchButtonClicked: and in parser:didEndElement:. You can simply remove these two lines.
Properties
If you look at the top of MainViewController.m, you’ll see that it uses a class extension to declare two private properties, searchResults and soundEffect:
@interface MainViewController () @property (nonatomic, retain) NSMutableArray *searchResults; @property (nonatomic, retain) SoundEffect *soundEffect; @end |
This is done primarily to make manual memory management easier and it’s a common reason why developers use properties. When you do,
self.searchResults = [NSMutableArray arrayWithCapacity:10]; |
the setter will take care of releasing the old value (if any) and properly retaining the new value. Developers have been using properties as a way of having to think less about when you need to retain and when you need to release. But now with ARC you don’t have to think about this at all!
In my opinion, using properties just for the purposes of simplifying memory management is no longer necessary. You can still do so if you want to but I think it’s better to just use instance variables now, and only use properties when you need to to make data accessible to other classes from your public interface.
Therefore, remove the class extension and the @synthesize statements for searchResults and soundEffect. Add new instance variables to replace them:
@implementation MainViewController { NSOperationQueue *queue; NSMutableString *currentStringValue; NSMutableArray *searchResults; SoundEffect *soundEffect; } |
Of course, this means we can no longer do self.searchResults and self.soundEffect. Change viewDidUnload to the following:
- (void)viewDidUnload { [super viewDidUnload]; self.tableView = nil; self.searchBar = nil; soundEffect = nil; } |
We still need to set soundEffect to nil because we do want to deallocate the SoundEffect object here. When the iPhone gets a low-memory warning, we should free as much memory as possible and the SoundEffect object is expendable at that point. Because instance variables create strong relationships by default, setting soundEffect to nil will remove the owner from the SoundEffect object and it will be deallocated immediately.
The soundEffect method now becomes:
- (SoundEffect *)soundEffect { if (soundEffect == nil) // lazy loading { soundEffect = [[SoundEffect alloc] initWithSoundNamed:@"Sound.caf"]; } return soundEffect; } |
That’s about as simple as we can make it. The SoundEffect object is allocated and assigned to the soundEffect instance variable. This variable becomes its owner and the object will stay alive until we set soundEffect to nil (in viewDidUnload), or until the MainViewController is deallocated.
In the rest of the file, replace anywhere where it says self.searchResults with just searchResults. When you build the app again, the only errors it should give are on the escape: method.
Note that in searchBarSearchButtonClicked, we still do:
[self.soundEffect play]; |
This will work even though we no longer have a property named soundEffect. The dot syntax isn’t restricted to just properties, although that’s what it is most commonly used for. If using dot syntax here offends you, you can change this line to:
[[self soundEffect] play]; |
Don’t change it to this, though:
[soundEffect play]; |
Because we use lazy loading to load the SoundEffect object, the soundEffect instance variable will always be nil until you call the soundEffect method. Therefore, to be certain we actually have a SoundEffect object, you should always access it through self. If you feel that this pattern does morally require you to declare soundEffect as a @property, then go right ahead. Different strokes for different folks. :-)
As a best practice, if you define something as a property, then you should always use it as a property. The only places where you should access the property’s backing instance variable directly are in init and when you provide a custom getter and setter. Anywhere else you should access the property through self.propertyName. That is why synthesize statements often rename the ivar:
@synthesize propertyName = _propertyName; |
This construct will prevent you from accidentally using the backing instance variable by typing “propertyName” when you meant to use “self.propertyName”.
Speaking of properties, MainViewController still has two outlet properties in its .h file:
@property (nonatomic, retain) IBOutlet UITableView *tableView; @property (nonatomic, retain) IBOutlet UISearchBar *searchBar; |
The retain keyword for properties still works with ARC and is simply a synonym for strong. However, it is better to call your properties strong because that’s the proper term from now on. But for these two particular properties I have other plans. Instead of strong, we will declare them as weak:
@property (nonatomic, weak) IBOutlet UITableView *tableView; @property (nonatomic, weak) IBOutlet UISearchBar *searchBar; |
Weak is the recommended relationship for all *outlet* properties. These view objects are already part of the view controller’s view hierarchy and don’t need to be retained elsewhere. The big advantage of declaring your outlets weak is that it saves you time writing the viewDidUnload method.
Currently our viewDidUnload looks like this:
- (void)viewDidUnload { [super viewDidUnload]; self.tableView = nil; self.searchBar = nil; soundEffect = nil; } |
You can now simplify it to the following:
- (void)viewDidUnload { [super viewDidUnload]; soundEffect = nil; } |
That’s right, because the tableView and searchBar properties are weak, they are automatically set to nil when the objects they point to are destroyed. That’s why we call them “zeroing” weak pointers.
When the iPhone receives a low-memory warning, the view controller’s main view gets unloaded, which releases all of its subviews as well. At that point the UITableView and UISearchBar objects cease to exist and the zeroing weak pointer system automatically sets self.tableView and self.searchBar to nil. There is no more need to do this ourselves in viewDidUnload. In fact, by the time viewDidUnload gets called these properties already are nil. I shall demonstrate this soon, but for that we need to add a second screen to the application.
This doesn’t mean you can completely forget about viewDidUnload. Remember, as long as you keep a pointer to an object, it stays alive. If you no longer want to hang on to objects, you need to set their pointers to nil. That’s exactly what we do for soundEffect. We don’t want to delete the searchResults array at this point but if we did then we would also set that to nil here. Any data that you don’t need that isn’t a weak outlet property you still need to nil out in viewDidUnload! The same goes for didReceiveMemoryWarning.
So from now on, make your outlet properties weak. The only outlets that should be strong are the ones from File’s Owner that are connected to top-level objects in the nib.
To summarize, the new modifiers for properties are:
- strong. This is a synonym for the old “retain”. A strong property becomes an owner of the object it points to.
- weak. This is a property that represents a weak pointer. It will automatically be set to nil when the pointed-to object is destroyed. Remember, use this for outlets.
- unsafe_unretained. This is a synonym for the old “assign”. You use it only in exceptional situations and when you want to target iOS 4. More about this later.
copy. This is still the same as before. It makes a copy of the object and creates a strong relationship. - assign. You’re no longer supposed to use this for objects, but you still use it for primitive values such as BOOL, int, and float.
Before ARC, you were able to write this:
@property (nonatomic, readonly) NSString *result; |
which would implicitly create an assign property. That used to be fine for readonly values. After all, what does retained mean when you talk about read-only data? However, with ARC the above will give the following error:
"ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute"
You must explicitly state whether you want this property to be strong, weak or unsafe_unretained. Most of the time, strong is the proper answer:
@property (nonatomic, strong, readonly) NSString *result; |
Earlier I mentioned that if you declare a property you should always access it through self.propertyName rather than through the backing instance variable (except in init and in any custom getter or setter methods). This is especially true for readonly properties. ARC can get confused if you modify such properties by changing their instance variables and strange bugs will result. The correct way is to redefine the property as readwrite in a class extension.
In your .h:
@interface WeatherPredictor @property (nonatomic, strong, readonly) NSNumber *temperature; @end |
In your .m:
@interface WeatherPredictor () @property (nonatomic, strong, readwrite) NSNumber *temperature; @end |
Toll-Free Bridging
Let’s fix that one last method so we can run the app again.
This method uses the CFURLCreateStringByAddingPercentEscapes() function to URL-encode a string. We use it to make sure any spaces or other characters in the search text that the user types get converted to something that is valid for use in an HTTP GET request.
The compiler gives several errors:
- Cast of C pointer type ‘CFStringRef’ to Objective-C pointer type ‘NSString *’ requires a bridged cast
- Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ requires a bridged cast
- Semantic Issue: ‘autorelease’ is unavailable: not available in automatic reference counting mode
- Automatic Reference Counting Issue: ARC forbids explicit message send of
‘autorelease’
These last two errors are really the same and simply mean that we cannot do [autorelease]. Let’s get rid of that first. What remains is this:
- (NSString *)escape:(NSString *)text { return (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)text, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
The other two errors have to do with casts that apparently should be “bridged”. There are three casts in this method:
- (NSString *)CFURLCreateStringByAddingPercentEscapes(…)
- (CFStringRef)text
- (CFStringRef)@”!*’();:@&=+$,/?%#[]“
The compiler only complains about the first two. The third one is a cast of a constant object and that doesn’t require any special memory management. This is a string literal that will be baked into the application executable. Unlike “real” objects, it is never allocated or freed.
If you wanted to, you could also write this as:
CFSTR("!*'();:@&=+$,/?%#[]") |
The CFSTR() macro creates a CFStringRef object from the specified string. The string literal is a regular C string now and therefore doesn’t begin with the @ sign. Instead of making an NSString object and casting it to a CFStringRef, we directly make a CFStringRef object. Which one you like better is largely a matter of taste, as they both deliver the exact same results.
Bridged casts are necessary when you move an object between worlds. On the one hand there is the world of Objective-C, on the other there is Core Foundation.
For most apps these days there isn’t a big need to use Core Foundation, you can do almost anything you want from comfortable Objective-C classes. However, some lower-levels APIs such as Core Graphics and Core Text are based on Core Foundation and it’s unlikely there will ever be an Objective-C version of them. Fortunately, the designers of iOS made it really easy to move certain objects between these two different kingdoms. And you won’t be charged at thing!
For all intents and purposes, NSString and CFStringRef can be treated as the being same. You can take an NSString object and use it as if it were a CFStringRef, and take a CFStringRef object and use it as if it were an NSString. That’s the idea behind toll-free bridging. Previously that was as simple as doing a cast:
CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; |
Of course, you also had to remember to release the object when you were done with it:
CFRelease(s1); |
The other way around, from Core Foundation to Objective-C, was just as easy:
CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault, bytes, kCFStringEncodingMacRoman); NSString *s3 = (NSString *)s2; // release the object when you're done [s3 release]; |
Now that we have ARC, the compiler needs to know who is responsible for releasing such casted objects. If you treat an NSObject as a Core Foundation object, then it is no longer ARC’s responsibility to release it. But you do need to tell ARC about your intentions, the compiler cannot infer this by itself. Likewise, if you create a Core Foundation object but then cast it to an NSObject, you need to tell ARC to take ownership of it and delete that object when its time comes. That’s what the bridging casts are for.
Let’s look at the simple case first. The CFURLCreateStringByAddingPercentEscapes() function takes a bunch of parameters, two of which are CFStringRef objects. These are the Core Foundation equivalent of NSString. Previously we could just cast these NSString objects into a CFStringRef but with ARC the compiler needs more information. We’ve already dealt with the constant string, which requires no bridging cast because it is a special object that will never be released. However, the text parameter is another story.
The text variable is an NSString object that was given to this method as a parameter. Like local variables, method parameters are strong pointers; their objects are retained upon entry to the method. The value from the text variable will continue to exist until the pointer is destroyed. Because it is a local variable, that happens when the escape: method ends.
We want ARC to stay the owner of this variable but we also want to temporarily treat it as a CFStringRef. For this type of situation, the __bridge specifier is used. It tells ARC that no change in ownership is taking place and that it should release the object using the normal rules.
We’ve already used __bridge before in SoundEffect.m:
OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &theSoundID); |
The exact same situation applies there. The fileURL variable contains an NSURL object and is managed by ARC. The AudioServicesCreateSystemSoundID() function, however, expects a CFURLRef object. Fortunately, NSURL and CFURLRef are toll-free bridged so we can cast the one into the other. Because we still want ARC to release the object when we’re done with it, we use the __bridge keyword to indicate that ARC remains in charge.
Change the escape: method to the following:
- (NSString *)escape:(NSString *)text { return (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
That takes care of all but one of the errors.
Most of the time when you cast an Objective-C object to a Core Foundation object or vice versa, you’ll want to use __bridge. However, there are times when you do want to give ARC ownership or relieve ARC of its ownership. In that case there are two other bridging casts that you can use:
- __bridge_transfer: Give ARC ownership
- __bridge_retained: Relieve ARC of its ownership
There is one error remaining in our source file, on the line:
return (NSString *)CFURLCreateStringByAddingPercentEscapes( |
If you click on the error message, the following Fix-it will pop up:
It gives two possible solutions: __bridge and __bridge_transfer. The correct choice here is __bridge_transfer. The CFURLCreateStringByAddingPercentEscapes() function creates a new CFStringRef object. Of course, we’d rather use NSString so we need to do a cast. What we’re really attempting to do is this:
CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .); NSString *s = (NSString *)result; return s; |
Because the function has the word “Create” in its name, it returns a retained object. Someone is responsible for releasing that retained object at some point. If we weren’t returning this object as an NSString, then our code may have looked something like this:
- (void)someMethod { CFStringRef result = CFURLCreateStringByAddingPercentEscapes (. . .); // do something with the string // . . . CFRelease(result); } |
Remember that ARC only works for Objective-C objects, not for objects that are created by Core Foundation. You still need to call CFRelease() on such objects yourself!
What we want to do in escape is convert that new CFStringRef object to an NSString object, and then ARC should automatically release that string whenever we’re no longer using it. But ARC needs to be told about this. Therefore, we use the __bridge_transfer modifier to say: “Hey ARC, this CFStringRef object is now an NSString object and we want you to dispose of it, so that we don’t have to call CFRelease() on it ourselves.”
The method now becomes:
- (NSString *)escape:(NSString *)text { return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); } |
We take the result from CFURLCreateStringByAddingPercentEscapes(), which is a CFStringRef object, and convert it into an NSString that is now under the care of ARC.
If we were to just use __bridge instead, then your app would have a memory leak. ARC doesn’t know that it should release the object when you’re done with it and no one calls CFRelease(). As a result, the object will stay in memory forever. It’s important that you pick the proper bridge specifier!
To make it a little easier to remember which type of bridge to use, there is a helper function named CFBridgingRelease(). It does the exact same thing as a __bridge_transfer cast but the meaning is clearer. The final version of the escape: method becomes:
- (NSString *)escape:(NSString *)text { return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes( NULL, (__bridge CFStringRef)text, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); } |
Instead of doing (__bridge_transfer NSString *) we now wrap CFURLCreateStringByAddingPercentEscapes() inside a call to CFBridgingRelease(). This CFBridgingRelease() function is defined as an inline function so it is no slower than doing the cast directly. It is named CFBridgingRelease() because you use it anywhere you would otherwise do a CFRelease() to balance the creation of the new object.
Another common framework that requires these bridging casts is the AddressBook framework. For example:
- (NSString *)firstName { return CFBridgingRelease(ABRecordCopyCompositeName(...)); } |
Anywhere you call a Core Foundation function named Create, Copy, or Retain you must do CFBridgingRelease() to safely transfer the value to ARC.
What about the other one, __bridge_retained? You would use that going the other way around. Suppose you have an NSString and you need to give that to some Core Foundation API that wants to take ownership of your string object. You don’t want ARC to also release that object, because then it would be released one time too many and apps have a tendency to crash when that happens. In other words, you use __bridge_retained to give the object to Core Foundation so that ARC is no longer responsible for releasing it. An example:
NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name]; CFStringRef s2 = (__bridge_retained CFStringRef)s1; // do something with s2 // . . . CFRelease(s2); |
As soon as the (__bridge_retained CFStringRef) cast happens, ARC considers itself no longer duty-bound to release the string object. If you had used __bridge in this example, then the app would likely crash. ARC might deallocate the string object before the Core Foundation code is done with it.
There is also a helper function for this kind of cast: CFBridgingRetain(). You can tell by the name that it makes Core Foundation do a retain on the object. The above example is better written as:
CFStringRef s2 = CFBridgingRetain(s1); // . . . CFRelease(s2); |
Now the meaning of the code is clearer. The call to CFRelease() is properly balanced by CFBridgingRetain(). I doubt you’ll need to use this particular bridge type often. Off the top of my head, I can’t think of a single API that is commonly used that requires this.
It is unlikely that you’ll have a lot of Core Foundation code in your apps. Most frameworks that you’ll use are Objective-C, with the exception of Core Graphics (which doesn’t have any toll-free bridged types), the Address Book, and the occasional low-level function. But if you do, the compiler will point out to you when you need to use a bridging cast.
Note: Not all Objective-C and Core Foundation objects that sound alike are toll-free bridged. For example, CGImage and UIImage cannot be cast to each another, and neither can CGColor and UIColor. This page lists the types that can be used interchangeably.
The __bridge casts are not limited to interactions with Core Foundation. Some APIs take void * pointers that let you store a reference to anything you want, whether that’s an Objective-C object, a Core Foundation object, a malloc()’d memory buffer, and so on. The notation void * means: this is a pointer but the actual datatype of what it points to could be anything.
To convert from an Objective-C object to void *, or the other way around, you will need to do a __bridge cast. For example:
MyClass *myObject = [[MyClass alloc] init]; [UIView beginAnimations:nil context:(__bridge void *)myObject]; |
In the animation delegate method, you do the conversion in reverse to get your object back:
- (void)animationDidStart:(NSString *)animationID context:(void *)context { MyClass *myObject = (__bridge MyClass *)context; . . . } |
We’ll see another example of this later in next chapter, where we cover using ARC with Cocos2D.
To summarize:
- When changing ownership from Core Foundation to Objective-C you use CFBridgingRelease().
- When changing ownership from Objective-C to Core Foundation you use CFBridgingRetain().
- When you want to use one type temporarily as if it were another without ownership change, you use __bridge.
That’s it as far as MainViewController is concerned. All the errors should be gone now and you can build and run the app. We won’t convert AFHTTPRequestOperation to ARC in this tutorial.
For the near future you may find that many of your favorite third-party libraries do not come in an ARC flavor yet. It’s no fun to maintain two versions of a library, one without ARC and one with, so I expect many library maintainers to pick just one. New libraries might be written for ARC only but older ones may prove too hard to convert. Therefore it’s likely that a portion of your code will remain with ARC disabled (the -fno-objc-arc compiler flag).
Fortunately, ARC works on a per-file basis so it’s no problem at all to combine these libraries with your own ARCified projects. Because it’s sometimes a bit of a hassle to disable ARC for a large selection of files, we’ll talk about smarter ways to put non-ARC third-party libraries into your projects in the next chapter.
Delegates and Weak Properties
The app you’ve seen so far is very simple and demonstrates only a few facets of ARC. To show you the rest, we’ll first have to add a new screen to the app.
Add a new UIViewController subclass to the project, with XIB for user interface, and name it DetailViewController.
Add two action methods to DetailViewController.h:
@interface DetailViewController : UIViewController - (IBAction)coolAction; - (IBAction)mehAction; @end |
We will connect these actions to two buttons in the nib. Open DetailViewController.xib and change the design to:
It just has a navigation bar and two buttons. Control-drag from each button to File’s Owner and connect their Touch Up Inside events to their respective actions.
In DetailViewController.m, add the implementation of the two action methods to the bottom. For now we’ll leave these methods empty:
- (IBAction)coolAction { } - (IBAction)mehAction { } |
We will make some changes to the main view controller so that it invokes this Detail screen when you tap on a search result. Change the didSelectRowAtIndexPath method in MainViewController.m to:
- (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [theTableView deselectRowAtIndexPath:indexPath animated:YES]; DetailViewController *controller = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil]; [self presentViewController:controller animated:YES completion:nil]; } |
This instantiates the DetailViewController and presents it on top of the current one.
Then add the following method:
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { if ([searchResults count] == 0) return nil; else return indexPath; } |
If there are no search results we put a single row into the table that says “(Nothing found)”. We don’t want to open the Detail screen when the user taps that row.
Because MainViewController doesn’t know anything about the DetailViewController class yet, we have to add an #import. Add this to MainViewController.h:
#import "DetailViewController.h"
|
(We’re adding it to the .h file and not the .m file for reasons that will soon become apparent.)
If you run the app now, tapping on a row brings up the Detail screen, but you cannot close it yet. The actions that are wired to the “Cool” and “Meh” buttons are still empty and pressing the buttons has no effect.
To fix this, we’ll give the Detail screen a delegate. That’s how you commonly make this type of arrangement work. If screen A invokes screen B, and screen B needs to tell A something — for example, that it needs to close — you make A a delegate of B. I’m sure you’ve seen this pattern before as it’s used all over the iOS API.
Change DetailViewController.h to the following:
#import <UIKit/UIKit.h> @class DetailViewController; @protocol DetailViewControllerDelegate <NSObject> - (void)detailViewController:(DetailViewController *)controller didPickButtonWithIndex:(NSInteger)buttonIndex; @end @interface DetailViewController : UIViewController @property (nonatomic, weak) id <DetailViewControllerDelegate> delegate; - (IBAction)coolAction; - (IBAction)mehAction; @end |
We’ve added a delegate protocol with a single method, as well as a property for that delegate. Notice that the property is declared “weak”. Making the delegate pointer weak is necessary to prevent ownership cycles.
You may be familiar with the concept of a retain cycle, where two objects retain each other so neither will ever be deallocated. That’s a common form of memory leak. In systems that employ garbage collection (GC) to handle their memory management, the garbage collector can recognize such cycles and release them anyway. But ARC is not garbage collection and for dealing with ownership cycles you’re still on your own. The weak pointer is an important tool for breaking such cycles.
The MainViewController creates the DetailViewController and presents it on the screen. That gives it a strong reference to this object. The DetailViewController in turn has a reference to a delegate. It doesn’t really care which object is its delegate but most of the time that will be the view controller that presented it, in other words MainViewController. So here we have a situation where two objects point at each other:
If both of these pointers were strong, then we would have an ownership cycle. It is best to prevent such cycles. The parent (MainViewController) owns the child (DetailViewController) through a strong pointer. If the child needs a reference back to the parent, through a delegate or otherwise, it should use a weak pointer.
Therefore, the rule is that delegates should be declared weak. Most of the time your properties and instance variables will be strong, but this is the exception.
In DetailViewController.m, synthesize the delegate:
@synthesize delegate;
|
Change the action methods to:
- (IBAction)coolAction { [self.delegate detailViewController:self didPickButtonWithIndex:0]; } - (IBAction)mehAction { [self.delegate detailViewController:self didPickButtonWithIndex:1]; } |
In MainViewController.h, add DetailViewControllerDelegate to the @interface line:
@interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, NSXMLParserDelegate, DetailViewControllerDelegate> |
(This is why we added the #import statement to the .h file earlier, instead of to the .m file.)
In MainViewController.m, change didSelectRowAtIndexPath to set the delegate property:
- (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [theTableView deselectRowAtIndexPath:indexPath animated:YES]; DetailViewController *controller = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil]; controller.delegate = self; [self presentViewController:controller animated:YES completion:nil]; } |
And finally, add the following to the bottom:
#pragma mark - DetailViewControllerDelegate - (void)detailViewController:(DetailViewController *)controller didPickButtonWithIndex:(NSInteger)buttonIndex { NSLog(@"Picked button %d", buttonIndex); [self dismissViewControllerAnimated:YES completion:nil]; } |
Here we simply dismiss the view controller. Run the app and try it out. Now you can press the Cool or Meh buttons to close the Detail screen.
Just to verify that DetailViewController gets released, give it a dealloc method that prints something to the Debug pane:
- (void)dealloc { NSLog(@"dealloc DetailViewController"); } |
In this case you could actually get away with making the delegate property strong (try it out if you don’t believe me). As soon as the MainViewController calls dismissViewControllerAnimated:, it loses the strong reference to DetailViewController. At that point there are no more pointers to that object and it will go away.
Still, it’s a good idea to stick to the recommended pattern:
- parent pointing to a child: strong
- child pointing to a parent: weak
The child should not be helping to keep the parent alive. We’ll see examples of ownership cycles that do cause problems when we talk about blocks in the second part of this tutorial.
The Detail screen isn’t very exciting yet but we can make it a little more interesting by putting the name of the selected artist in the navigation bar. Add the following to DetailViewController.h:
@property (nonatomic, strong) NSString *artistName; @property (nonatomic, weak) IBOutlet UINavigationBar *navigationBar; |
The artistName property will contain the name of the selected artist. Previously you would have made this a retain property (or copy), so now it becomes strong.
The navigationBar property is an outlet. As before, outlets that are not top-level objects in the nib should be made weak so they are automatically released in low-memory situations.
Synthesize these properties in DetailViewController.m:
@synthesize artistName; @synthesize navigationBar; |
Change viewDidLoad to:
- (void)viewDidLoad { [super viewDidLoad]; self.navigationBar.topItem.title = self.artistName; } |
Don’t forget to connect the navigation bar from the nib file to the outlet!
In MainViewController.m, change didSelectRowAtIndexPath to:
- (void)tableView:(UITableView *)theTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [theTableView deselectRowAtIndexPath:indexPath animated:YES]; NSString *artistName = [searchResults objectAtIndex:indexPath.row]; DetailViewController *controller = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil]; controller.delegate = self; controller.artistName = artistName; [self presentViewController:controller animated:YES completion:nil]; } |
Run the app and you’ll see the name of the artist in the navigation bar:
Often developers use copy properties for objects of classes such as NSString and NSArray. This is done to make sure no one can change that object after you have put it into the property. Even though an NSString object is immutable once created, the actual object given to the property could be an NSMutableString that can be modified afterward.
Using the copy modifier is still possible with ARC. If you’re slightly paranoid about your properties being truly immutable, then change the declaration of the artistName property to:
@property (nonatomic, copy) NSString *artistName; |
By adding the copy modifier, it makes it so that when we assign to the property like this:
controller.artistName = artistName;
|
the app first makes a copy of the string object from the local variable and then stores that copy into the property. Other than that, this property works exactly the same way as a strong reference.
Let’s see what happens when we log the values of artistName and navigationBar in the dealloc method:
- (void)dealloc { NSLog(@"dealloc DetailViewController"); NSLog(@"artistName '%@'", self.artistName); NSLog(@"navigationBar %@", self.navigationBar); } |
If you run the app and close the Detail screen you will see that both properties still have their values:
Artists[833:207] dealloc DetailViewController Artists[833:207] artistName 'Evans, Bill' Artists[833:207] navigationBar >
However, as soon as dealloc is over, these objects will be released and deallocated (since no one else is holding on to them). That is to say, the string object from artistName will be released and the UINavigationBar object is freed as part of the view hierarchy. The navigationBar property itself is weak and is therefore excluded from memory management.
Now that we have this second screen we can test the viewDidUnload method from MainViewController.m. To do this, add some NSLog() statements to that method:
- (void)viewDidUnload { [super viewDidUnload]; NSLog(@"tableView %@", self.tableView); NSLog(@"searchBar %@", self.searchBar); soundEffect = nil; } |
Run the app and open the Detail screen. Then from the Simulator’s Hardware menu, choose Simulate Memory Warning. In the Debug pane you should see this:
Artists[880:207] Received memory warning. Artists[880:207] tableView (null) Artists[880:207] searchBar (null)
Because tableView and searchBar are weak properties, these objects are only owned by the view hierarchy. As soon as the main view gets unloaded, it releases all its subviews. Because we don’t hold on to the UITableView and UISearchBar objects with strong pointers, these objects get deleted before viewDidUnload is invoked.
Just to see the difference, let’s make them strong references in MainViewController.h:
@property (nonatomic, strong) IBOutlet UITableView *tableView; @property (nonatomic, strong) IBOutlet UISearchBar *searchBar;
Run the app again and repeat the simulated memory warning. Now the Debug pane shows that the objects are still alive:
Artists[912:207] Received memory warning. Artists[912:207] tableView Artists[912:207] searchBar
It’s not necessarily wrong to make outlets strong but then you accept responsibility for setting these properties to nil by hand in viewDidUnload.
Unsafe_unretained
We’re almost done covering the basics of ARC – I just wanted to mention one more thing you should know.
Besides strong and weak there is another new modifier, unsafe_unretained. You typically don’t want to use that. The compiler will add no automated retains or releases for variables or properties that are declared as unsafe_unretained.
The reason this new modifier has the word “unsafe” in its name is that it can point to an object that no longer exists. If you try to use such a pointer it’s very likely your app will crash. This is the sort of thing you used the NSZombieEnabled debugging tool to find. Technically speaking, if you don’t use any unsafe_unretained properties or variables, you can never send messages to deallocated objects anymore.
Most of the time you want to use strong, sometimes weak, and almost never unsafe_unretained. The reason unsafe_unretained still exists is for compatibility with iOS 4, where the weak pointer system is not available, and for a few other tricks.
Let’s see how this works:
@property (nonatomic, unsafe_unretained) IBOutlet UITableView *tableView; @property (nonatomic, unsafe_unretained) IBOutlet UISearchBar *searchBar; |
Run the app and simulate the low-memory warning.
Artists[982:207] Received memory warning. Artists[982:207] *** -[UITableView retain]: message sent to deallocated instance 0x7033200
Whoops, the app crashes. An unsafe_unretained pointer does not have ownership over the object it points to. That means the UITableView was not kept alive by this pointer and it got deallocated before viewDidUnload was called (its only owner was the main view). If this was a true weak pointer, then its value would be been set to nil. Remember, that was the cool feature of “zeroing” weak pointers. We saw that earlier when the NSLog() said “(null)”.
However, unlike a true weak pointer, an unsafe_unretained pointer is not reset to nil when the associated object dies. It keeps its old value. When you try to send a message to the object — which is what happens when you NSLog() it — you’re sending the message to an object that no longer exists. Sometimes this may accidentally work, if the memory for that object hasn’t been overwritten yet by another object, but often it will crash your app… which is exactly what we saw happening here. That should illustrate why these things are called “unsafe”.
Note: we caught this bug because I enabled zombies in the Diagnostics tab for this scheme. To see this settings panel, choose Product -> Edit Scheme… from the menubar.
Without this setting, the app may not have crashed at all, or it may have crashed at some later point. Good luck trying to figure that one out! Those are tricky bugs to fix.
By the way, this is probably a good point to return the properties to weak:
@property (nonatomic, weak) IBOutlet UITableView *tableView; @property (nonatomic, weak) IBOutlet UISearchBar *searchBar; |
For ARC-enabled apps the Enable Zombie Objects setting (also known as NSZombieEnabled) isn’t terribly useful anymore, so you can disable it… except when you’re using unsafe_unretained pointers!
If it is so harmful then why use unsafe_unretained in the first place? A big reason is iOS 4.
Using ARC on iOS 4
Because ARC is largely a new feature of the LLVM 3.0 compiler and not of iOS 5, you can also use it on iOS 4.0 and up. The only part of ARC that does require iOS 5 are the weak pointers. That means if you wish to deploy your ARC app on iOS 4, you cannot use weak properties or __weak variables.
You don’t need to do anything special to make your ARC project work on iOS 4. If you choose a version of iOS 4 as your Deployment Target, then the compiler will automatically insert a compatibility library into your project that makes the ARC functionality available on iOS 4. That’s it, just pick iOS 4.x as the Deployment Target and you’re done.
If you use weak references anywhere in your code, the compiler will give the following error:
"Error: the current deployment target does not support automated __weak references"
|
You cannot use weak or __weak on iOS 4, so replace weak properties with unsafe_unretained and __weak variables with __unsafe_unretrained. Remember that these variables aren’t set to nil when the referenced object is deallocated, so if you’re not careful your variables may be pointing at objects that no longer exist. Be sure to test your app with NSZombieEnabled!