Beginning ARC in iOS 5 Tutorial Part 2

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 […] By Ray Wenderlich.

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

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:

descr

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.

descr

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:

descr

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:

descr

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 <UINavigationBar: 0x686d970; frame = (0 0; 320 44); autoresize = W+BM; layer = <CALayer: 0x686d9e0>>

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 <UITableView: 0xa816400; . . .>
Artists[912:207] searchBar <UISearchBar: 0x8821360; . . .>

It’s not necessarily wrong to make outlets strong but then you accept responsibility for setting these properties to nil by hand in viewDidUnload.

Contributors

Over 300 content creators. Join our team.