Making Your Classes Talk to Each Other, Part 2

This is the second part of a three-part tutorial for beginning iOS programmers on how to make classes, especially view controllers, communicate with each other. See also Part 1 and Part 3.

Version 4: Deleting smoothies

Often in a screen that has a table view, you are able to delete items by swiping a row or by tapping an Edit button in the navigation bar. Let’s add “swipe to delete” to our list of smoothies. (In a later chapter we’ll also add a way to create new smoothies.)

Swipe-to-delete is activated by a UITableViewDataSource method:

- (void)tableView:(UITableView*)theTableView
	commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
	forRowAtIndexPath:(NSIndexPath*)indexPath
{
}

If you just add this method to SmoothiesViewController.m, even though it doesn’t do anything yet, then swiping the row in the table will make the red Delete button appear.

Figure 4.1. Swipe-to-delete in action.

Figure 4.1. Swipe-to-delete in action.

The commitEditingStyle method is called when the user actually presses the Delete button. Two things need to happen then: 1) we must remove the Recipe in question from our data model; 2) we must tell the table view to remove the row.

This is how we do that:

// in SmoothiesViewController.m:
- (void)tableView:(UITableView*)theTableView
	commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
	forRowAtIndexPath:(NSIndexPath*)indexPath
{
	// Remove the Recipe from our data model (the list of recipes)
	[self.recipes removeObjectAtIndex:indexPath.row];
 
	// Remove the row from the table with an animation
	[theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

Very simple and it looks pretty good too. Note that the recipes aren’t gone forever. The next time you start the app, they are back again. That is because we always create a new list of recipes when the SmoothiesViewController is loaded.

The DataModel class

I want to make a fundamental change now to the app. So far when we’ve talked about the data model, it concerned two items: the Recipe class, which describes a single recipe, and the NSMutableArray that contains a list of those Recipe objects.

In SmoothiesViewController, we often use self.recipes directly: when the controller is loaded, we add several Recipe objects into this list; the table view data source loads the cells from this list; and now the swipe-to-delete method removes items directly from this list.

So far it has been manageable, but now I want to abstract away these operations and hide the list. To that end, we’ll make a new class named DataModel. This is our top-level data model class. For the time being it will just have the list of recipes, but in later chapters we’ll give it more responsibilities.

What should the DataModel class do?

  • It owns the list of recipes.
  • It is able to tell how many recipes are in the list. We need this for the table view data source so it knows how many rows there are in the table.
  • It can return a Recipe object at a specific position in the list. Also needed for the table view.
  • It can delete a Recipe from the list.

I used Xcode’s New File wizard to create an Objective-C class that is a subclass of NSObject and named it DataModel. In DataModel.h, I added methods for the tasks we outlined above:

@class Recipe;
 
@interface DataModel : NSObject
{
}
 
// Returns how many recipes are in the list.
- (int)recipeCount;
 
// Returns the Recipe object at the specified position in the list.
- (Recipe*)recipeAtIndex:(int)index;
 
// Deletes a Recipe object from the list.
- (void)removeRecipeAtIndex:(int)index;
 
@end

We already have the code for these methods because SmoothiesViewController already does these things. So I was able to cut-and-paste a bunch of code from SmoothiesViewController to DataModel.m.

At this point, DataModel does no more than what NSMutableArray does. The advantage is that it hides from SmoothiesViewController (and anyone else) the fact that it uses an NSMutableArray underneath. In fact, it does a lot less than NSMutableArray and that’s exactly what we want. You can’t modify the list of recipes unless DataModel has a method for it. This encapsulation and abstraction makes our code more robust. We already talked a bit about this in the previous chapter.

That DataModel uses an NSMutableArray is a so-called “implementation detail”. It’s important to the implementation of DataModel, but not to anyone who uses DataModel. It’s a good strategy to hide implementation details from the rest of your code. Always ask yourself: is this something I can hide in my class because no one needs to know about it?

Private properties

Note that DataModel‘s @interface does not have an NSMutableArray property or even instance variable. Then where do we keep the list of recipes? I used a trick called a “class extension” for this.

Maybe you’re familiar with the concept of Objective-C’s categories. A category lets you add new methods to an existing class, which can be very convenient. A class extension is like that, although you use it in your .m file only and it allows you to add properties. Because this property is not in your .h file, it’s private.

Since we don’t want anyone to know that we use an NSMutableArray to store the list of recipes – or depend on this – we declare it as such a private property at the top of DataModel.m:

// Yes, this goes into DataModel.m:
@interface DataModel ()
 
// The list of Recipe objects
@property (nonatomic, retain) NSMutableArray* recipes;
 
@end

Simplifying SmoothiesViewController

If you compare SmoothiesViewController to the previous versions, it’s now a lot simpler. We moved a lot of the logic into DataModel. Instead of a list of recipes, it now just has a dataModel property.

Where previously we did:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of recipes in our list
	return self.recipes.count;
}

We now do:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of recipes in our list
	return [self.dataModel recipeCount];
}

Likewise, swipe-to-delete now becomes:

- (void)tableView:(UITableView*)theTableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath
{
	// Remove the Recipe from our data model
	[self.dataModel removeRecipeAtIndex:indexPath.row];
 
	// Remove the row from the table with an animation
	[theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

The reason for making this change is that a view controller’s job isn’t to manage the data, that is really the job for a separate data model object. The view controller also doesn’t draw directly on the screen; that is the job for a view. The actual job of the controller is to let the view and the data model talk to each other. That’s why it is better to separate these jobs into different classes. This keeps everything well-organized.

If this is all our app would do, then using a separate DataModel class instead of managing the list ourselves would be overkill. But we’ll be adding a whole bunch of new features soon that make having this class indispensable.

The class structure now looks like this:

Figure 4.2. The DataModel class has replaced NSMutableArray. It still uses an NSMutableArray internally to store the Recipe objects, but that is of no concern to the rest of the app.

Figure 4.2. The DataModel class has replaced NSMutableArray. It still uses an NSMutableArray internally to store the Recipe objects, but that is of no concern to the rest of the app.

Download the source code for version 4

Version 5: Favorites!

Let’s make the app a little more interesting and add a Favorites screen. Even though the current app only has three smoothies and thus hardly needs a new screen to help you keep track of your favorites, this exercise will illustrate a number of important concepts that you’ll see over and over again in real apps.

When we’re done the app will look like this:

Figure 5.1. We've added a tab bar and a Favorites screen.

Figure 5.1. We've added a tab bar and a Favorites screen.

I decided to add a tab bar to the app because that makes it easy to switch between the main list of smoothies and the Favorites screen, and because it’s a common design for iPhone apps.

Previously, the top-level controller in our window was the navigation controller that held SmoothiesViewController. Now we have to restructure this a little. I added a tab bar controller to the nib and connected that to the SmoothiesAppDelegate (and thus, the window) instead. The navigation controller is then added to the tab bar controller.

Figure 5.2. The new MainWindow.xib

Figure 5.2. The new MainWindow.xib

The second tab has a navigation controller of its own, which in turn contains the FavoritesViewController. Just like the main Smoothies screen, the Favorites screen is also a list of smoothies, so it also extends from UITableViewController.

There is another user interface problem to solve: how do we let users mark a smoothie as a favorite? For this, I added a button on the Recipe Details screen:

Figure 5.3. The Recipe Details screen now has a “Favorite” toggle button.

Figure 5.3. The Recipe Details screen now has a “Favorite” toggle button.

It will work as a toggle button. Tap it once to mark the recipe as a favorite, tap it again to remove it from the favorites. When the user taps this button, the smoothie will immediately appear in the Favorites screen. If the user changes his mind and taps the button again, the smoothie disappears from the Favorites screen.

Saving the favorites

Where and how do we store which smoothies are favorites? There are several ways we could do this. We want to remember these favorites even after the user quits the app, so we must store it in a file somehow and then load the file back in when the app starts again. We could use an XML file, a Plist file, or even an SQLite database. However, there is also the very handy NSUserDefaults class. This class is often used to store user settings and is very convenient to use. It works like a dictionary.

In RecipeDetailsViewController.m, we add the following method:

- (IBAction)favoriteTapped
{
	NSArray* favorites = [[NSUserDefaults standardUserDefaults] objectForKey:@"Favorites"];
	if ([favorites containsObject:self.recipe.name])
	{
		// un-favorite this recipe
	}
	else
	{
		// add this recipe to the favorites
	}
}

This asks the NSUserDefaults for an object named “Favorites”. That’s just a name I picked. Because NSUserDefaults works as a dictionary, it stores each object under a key and “Favorites” will be the key for our object. The type of our object is NSArray because it makes sense to store the favorites as an array of names.

After we’ve obtained our NSArray from NSUserDefaults, the code looks if the name of the current recipe (self.recipe.name) is already in this list. If yes, then we have to un-favorite this recipe. If the recipe wasn’t in the favorites yet, then we add it.

Hold on a minute… why do we store a list with just the names of the recipes here and not the entire Recipe object? First of all, we can’t store our own classes in NSUserDefaults, only standard objects such as arrays and strings. But more importantly, if we stored an entire Recipe object then we would be saving a copy of the object, not the object itself.

Instead, it is better to store something that lets us refer to a Recipe object from the main data model, because that’s what is being used in the Recipe Details screen. If your data objects have an unique ID, then you could use that. Our recipes don’t have an ID, but their name is going to be unique so that will work just as well.

You might be tempted to store the pointer to the Recipe object instead. After all, that is going to be unique too and it makes it really easy to find the corresponding Recipe object. But consider what happens when the app exits and is started again. We will have completely new Recipe instances, with new pointers. The pointers that you stored in NSUserDefaults will no longer point to the correct Recipes and your app will most likely crash hard.

So the main data model stores a list of Recipe objects, while the favorites are stored as a list of recipe names. We can use these names to find the corresponding Recipe objects.

Defaults for the defaults

There is something else we need to do before we can use the above method. The very first time the app is run, NSUserDefaults does not have anything stored under the key “Favorites” yet. The above code assumes there is always an NSArray there, which is currently not guaranteed. We could solve this problem by checking if the value is nil and then create the array and add it, but there is an easier method.

Add the following to SmoothieAppDelegate.m:

+ (void)initialize
{
	if ([self class] == [SmoothiesAppDelegate class])
	{
		NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSArray array], @"Favorites",
			nil];
 
		[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
	}
}

The initialize method is called when the class is first used. We take advantage of this to set defaults on NSUserDefaults; in this case an empty NSArray for the key “Favorites”. This default value will be used if NSUserDefaults does not contain the key “Favorites” yet. Now we are guaranteed that [objectForKey:@"Favorites"] will return a valid NSArray object.

To add a recipe to the list of favorites, we do:

NSMutableArray* newArray = [NSMutableArray arrayWithArray:favorites];
	[newArray addObject:self.recipe.name];
	[[NSUserDefaults standardUserDefaults] setObject:newArray forKey:@"Favorites"];
[[NSUserDefaults standardUserDefaults] synchronize];

Because the array we received from NSUserDefaults is immutable, we have to create a new array with the contents of the old one and add the recipe’s name to it. Finally, we put the new array in NSUserDefaults.

Just for good measure, we “synchronize” the NSUserDefaults. This causes the new settings to be written to disk immediately. Calling synchronize isn’t strictly necessary because NSUserDefaults will sync itself periodically and when the app exits or goes to the background, but not when you exit the app from within Xcode.

Removing a recipe from the favorites works similarly.

Let’s display the favorites in the Favorites screen, as that’s what it is there for. In the next chapter we’ll look at a better solution but for now we’ll simply read the list of recipe names from NSUserDefaults and show them in the table.

In FavoritesViewController.m:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	NSArray* favorites = [[NSUserDefaults standardUserDefaults] objectForKey:@"Favorites"];
	return favorites.count;
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	...
 
	NSArray* favorites = [[NSUserDefaults standardUserDefaults] objectForKey:@"Favorites"];
	NSString* recipeName = [favorites objectAtIndex:indexPath.row];
 
	cell.textLabel.text = recipeName;
	return cell;
}

We’ll refresh the table whenever the Favorites screen becomes visible, so if you click the “Favorite” button in the Recipe Details screen and then switch to the Favorites tab, the list is immediately updated with any changes.

Again in FavoritesViewController.m:

- (void)viewWillAppear:(BOOL)animated
{
	[super viewWillAppear:animated];
	[self.tableView reloadData];
}

There is one more thing to do. In the Recipe Details screen it would be nice to show whether the recipe is currently a favorite. If it is, we add a checkmark in front of the title, like so:

// in RecipeDetailsViewController.m:
- (void)updateFavoriteButton
{
	NSString* title;
	NSArray* favorites = [[NSUserDefaults standardUserDefaults] objectForKey:@"Favorites"];
	if ([favorites containsObject:self.recipe.name])
		title = @"√ Favorite";
	else
		title = @"Favorite";
 
	[self.favoriteButton setTitle:title forState:UIControlStateNormal];
}

This method is called whenever we press the button (in favoriteTapped) but also when the Recipe Details screen is loaded, so we set the button in the proper initial state.

The class diagram has become a little bigger now:

Figure 5.4. We've added a tab bar controller and the Favorites view controller.

Figure 5.4. We've added a tab bar controller and the Favorites view controller.

To be honest, the implementation of how we store favorites is a little messy. NSUserDefaults really is part of the data model, so in the next chapter we’ll abstract it away in the DataModel class.

Download the source code for version 5

Version 6: Putting the favorites in the data model

You probably wondered why on earth we were storing stuff directly in NSUserDefaults in the previous chapter. That became kind of messy really quickly. Indeed, a better place to do this is… in the DataModel class. That is exactly why we have that class. Favorites certainly count as data objects, so their proper place is in DataModel.

These are the tasks that we need to do with our favorites:

  • We need to know how many there are. This is required by the Favorite screen’s table view.
  • We need to retrieve the Recipe objects that are marked as favorite. Also for the table view.
  • Determine whether a Recipe is currently favorite.
  • Add a new Recipe to the favorites.
  • Remove a Recipe from the favorites.

Let’s add a method for each of these tasks to DataModel:

- (int)favoritesCount;
- (Recipe*)favoriteAtIndex:(int)index;
- (BOOL)isFavorite:(Recipe*)recipe;
- (void)addToFavorites:(Recipe*)recipe;
- (void)removeFromFavorites:(Recipe*)recipe;

In order for RecipeDetailsViewController to be able to use this, it needs to know about the DataModel class. So we add a property:

// in RecipeDetailsViewController.h:
@property (nonatomic, assign) DataModel* dataModel;

Now when SmoothiesViewController creates the RecipeDetailsViewController in response to a tap on a row in the table, we assign the data model object to it:

// in SmoothiesViewController.m:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
	RecipeDetailsViewController* controller = [[RecipeDetailsViewController alloc] init];
 
	// Assign the data model object to the controller
	controller.dataModel = self.dataModel;
 
	Recipe* recipe = [dataModel recipeAtIndex:indexPath.row];
	controller.recipe = recipe;
 
	[self.navigationController pushViewController:controller animated:YES];
	[controller release];
}

The code for RecipeDetailsViewController is updated to look as follows:

// in RecipeDetailsViewController.m:
- (IBAction)favoriteTapped
{
	if ([self.dataModel isFavorite:self.recipe])
	{
		[self.dataModel removeFromFavorites:self.recipe];
	}
	else
	{
		[self.dataModel addToFavorites:self.recipe];
	}
 
	[self updateFavoriteButton];
}

That is a lot cleaner than what we had before! All the hassle of dealing with NSUserDefaults is now hidden away in DataModel.

Weak and strong references

Note that we declared the dataModel property in RecipeDetailsViewController “assign” instead of “retain”. If you’ve done any iOS programming you should be aware of retain and the memory management rules.

In short: the owner of an object will retain that object and is ultimately responsible for releasing it when it no longer uses that object. When an object is retained, this is called a strong reference. SmoothieViewController has a strong reference to DataModel, because it created that object. SmoothieViewController owns the DataModel for as long as it is alive (which is until this app exits) and will release it in its dealloc.

When we pass the DataModel instance to RecipeDetailsViewController, we make it a so-called weak reference; RecipeDetailsViewController does not own the object and therefore does not retain it. Because RecipeDetailsViewController is guaranteed that the DataModel instance will be around for at least as long as it is (i.e. until the user exits the app), it has no need to retain it.

In general, if you pass along pointers to objects you should make them weak references (using the “assign” property semantics), to signify that the object you are giving it to does not assume ownership. You only use “retain” or “copy” when you are not guaranteed that the object may stick around, in which case you become responsible for releasing it when the time comes.

Where do we put DataModel?

The other place that used NSUserDefaults directly is FavoritesViewController. So let’s change that. But wait a minute… DataModel is currently created and owned by SmoothiesViewController but now FavoritesViewController also needs to use it. The problem is that these classes don’t know anything about each other. And preferably I would like to keep it that way (separation of concerns, remember?). So how can we make FavoritesViewController use the DataModel?

Obviously SmoothiesViewController is no longer the proper place to create the DataModel. I did that before because it was our only view controller, so it made sense. But now that we have another view controller that also needs to use the DataModel object, we have to refactor this code a little.

There are generally four ways to share your data model code among your controllers:

  1. Make the shared data a global variable
  2. Make the shared data a singleton
  3. Put the shared data in your application delegate
  4. Make one class the owner of the object and pass a (weak) reference to any other class that needs to use the object

The first three are variations on the same theme and I’ll explain below how to do that but also why you really shouldn’t.

Global variables

A global variable is a variable that can be accessed from anywhere in your code. We can make our DataModel object a global by making these changes:

In DataModel.h, below the interface definition:

@interface DataModel : NSObject
...
@end
 
extern DataModel* dataModel;

The “extern” line tells any code that #imports DataModel.h that there is a global variable named dataModel that points to an instance of a DataModel object.

The extern declaration just said that there was a dataModel variable defined somewhere, but it didn’t actually define it yet. We do that in DataModel.m, near the top:

#import "DataModel.h"
 
DataModel* dataModel = nil;
 
@implementation DataModel
...
@end

We still need to create the instance of DataModel and put it into that variable because currently it is nil. The app delegate is a logical place for that:

// in SmoothieAppDelegate.m:
#import "DataModel.h"
 
@implementation SmoothieAppDelegate
 
+ (void)initialize
{
	if ([self class] == [SmoothiesAppDelegate class])
	{
		...
 
		dataModel = [[DataModel alloc] init];
	}
}

Because we #import DataModel.h, our app delegate knows about the global variable dataModel and can put values into it.

Now in FavoritesViewController‘s table data source we do:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of favorites
	return [dataModel favoritesCount];
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	... make a UITableViewCell ...
 
	// Get the favorite Recipe from the list
	Recipe* recipe = [dataModel favoriteAtIndex:indexPath.row]; 
 
	// Put the Recipe data into the cell
	cell.textLabel.text = recipe.name;
	cell.imageView.image = recipe.image;
 
	return cell;
}

Note that the favoriteAtIndex: method now returns a Recipe object, not just the name of the Recipe. This lets us put the recipe’s image into the cell as well.

Pretty sweet, right? Well, there is a problem: using globals is considered bad programming practice. It generally makes programs harder to understand. The value of dataModel can be changed at any point in the program, and then any other objects that depend on dataModel might start to behave strangely if they did not expect that change. In our small app that doesn’t happen but when a program grows larger and starts to use more and more globals, it becomes a lot harder to keep track of what is going on.

Now, I admit that I use global variables from time to time. Occasionally they are the best way to solve a certain problem. Most of the time, however, they aren’t. Whenever you are tempted to use a global variable, try to find a cleaner solution first.

You can find the source code for this solution in Version 6A.

Singleton

The singleton is a controversial design pattern because it is basically a global variable hidden in a class. The iOS API uses singletons in various places. We’ve already seen one when we used NSUserDefaults.

There are two ways to make singletons:

  1. Use only class methods. This class will not have an instance. This method isn’t very common as it is a little cumbersome to use.
  2. Make a single instance of the class behind the scenes and use a class method that gives access to it. This is what [NSUserDefaults standardUserDefaults] does.

I’ll demonstrate how to do the latter. Add the following method to DataModel.m:

+ (id)sharedInstance
{
	static DataModel* instance = nil;
	if (instance == nil)
	{
		instance = [[self alloc] init];
	}
	return instance;
}

If you want to use DataModel anywhere, you use [DataModel sharedInstance]. This only creates a new DataModel instance once, the first time you call it.

FavoritesController‘s data source method now becomes:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of favorites
	return [[DataModel sharedInstance] favoritesCount];
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	... make a UITableViewCell ...
 
	// Get the favorite Recipe from the list
	Recipe* recipe = [[DataModel sharedInstance] favoriteAtIndex:indexPath.row]; 
 
	// Put the Recipe data into the cell
	cell.textLabel.text = recipe.name;
	cell.imageView.image = recipe.image;
 
	return cell;
}

Similar changes were made to SmoothiesViewController.

This is only a simple implementation of the singleton pattern. There is nothing stopping you from doing:

DataModel* anotherDataModel = [[DataModel alloc] init];

Apple has some sample code that can help prevent this, but I never find that worth implementing. Just stick to the convention that your code has to use this class through the +sharedInstance method.

The main problem with singletons is that, just like global variables, they make it harder to isolate parts of the code. It is often desirable to keep the scope of objects as small as possible, i.e. to restrict the use and visibility of an object to only the portion of the code that depends on it. But a global as well as a singleton are visible anywhere in the code. This increases the general complexity of the code and thus makes it harder to understand. Again, for a small app such as this using a singleton won’t get you into trouble, but there is a better way that I will be showing you soon. First, however, a worse way.

You can find the singleton source code in Version 6B.

Application delegate

As I said, iOS itself uses singletons in various places and one of these is UIApplication. Because UIApplication has a delegate that you already have to write a class for, many people treat their app delegate as a kind of singleton too. They like to put shared data in their app delegate class, simply because it is convenient.

Here is how that works. We add DataModel as a property to SmoothieAppDelegate, and create the instance when the app starts up:

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
	// Create the Data Model object
	self.dataModel = [[[DataModel alloc] init] autorelease];
 
	...
}

Then to use this shared DataModel object in our code, we do:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of favorites
	DataModel* dataModel = (SmoothieAppDelegate*)[[UIApplication sharedApplication] delegate];
	return [dataModel favoritesCount];
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	... make a UITableViewCell ...
 
	// Get the favorite Recipe from the list
	DataModel* dataModel = (SmoothieAppDelegate*)[[UIApplication sharedApplication] delegate];
	Recipe* recipe = [dataModel favoriteAtIndex:indexPath.row]; 
 
	// Put the Recipe data into the cell
	cell.textLabel.text = recipe.name;
	cell.imageView.image = recipe.image;
 
	return cell;
}

Everywhere we want to use the data model, we now have to do this first:

DataModel* dataModel = (SmoothieAppDelegate*)[[UIApplication sharedApplication] delegate];

And we have to #import “SmoothieAppDelegate.h” in every file where we want to use DataModel.

Yuck. Because that is ugly, some people even go as far as this:

#define AD ((SmoothieAppDelegate*)[[UIApplication sharedApplication] delegate])
 
Recipe* recipe = [AD.dataModel favoriteAtIndex:indexPath.row];

Please, don’t resort to such atrocities! It makes me sad to see code like that. :(

Just because you already have an AppDelegate class and that you can access it more or less as a singleton, doesn’t mean that this is the proper place for your data model code.

I did not make a source code example for this, because this approach is horrible! I really want to dissuade you from using it. On to the proper solution…

Passing references

The idea is simple: there will be one owner of DataModel. In our case, we’ll make our app delegate the owner. There is nothing wrong with that. But instead of always asking the app delegate for what the data model is, the app delegate will pass a reference to the data model to any class that needs to use it. We already did something similar before when SmoothieViewController passed its DataModel reference on to RecipeDetailsViewController.

In SmoothieAppDelegate.h we add the following:

@class DataModel;
@class SmoothiesViewController;
@class FavoritesViewController;
 
@interface SmoothiesAppDelegate : NSObject 
 
...
 
@property (nonatomic, retain) IBOutlet SmoothiesViewController* smoothiesViewController;
@property (nonatomic, retain) IBOutlet FavoritesViewController* favoritesViewController;
 
// The shared Data Model object
@property (nonatomic, retain) DataModel* dataModel;
 
@end

We added the DataModel as a retained property, and we added two new IBOutlets so we can refer to the SmoothiesViewController and FavoritesViewController from the app delegate. I connected these outlets in Interface Builder.

We need those outlets because that is how we pass the DataModel reference around:

// in SmoothieAppDelegate.m:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
	// Create the Data Model object
	self.dataModel = [[[DataModel alloc] init] autorelease];
 
	// Tell the view controllers about the Data Model
	self.smoothiesViewController.dataModel = self.dataModel;
	self.favoritesViewController.dataModel = self.dataModel;
 
	[self.window addSubview:self.tabBarController.view];
	[self.window makeKeyAndVisible];
	return YES;
}

Both SmoothiesViewController and FavoritesViewController now have a property for the data model:

// in SmoothiesViewController.h and FavoritesViewController.h:
@property (nonatomic, assign) DataModel* dataModel;

This is a weak reference because these objects don’t own the data model instance; they just use it. Because the app delegate class will be around until the app exits, so will the data model object.

As you can see, this approach requires that you make a few more properties than if you were to use a global variable or singleton, but it’s a cleaner solution. The Smoothies and Favorites view controllers don’t care where the data model object comes from; they just know that it’s there when they need it and that they can access it through their self.dataModel property:

- (NSInteger)tableView:(UITableView*)theTableView numberOfRowsInSection:(NSInteger)section
{
	// Return the number of favorites
	return [self.dataModel favoritesCount];
}
 
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
	... make a UITableViewCell ...
 
	// Get the favorite Recipe from the list
	Recipe* recipe = [self.dataModel favoriteAtIndex:indexPath.row]; 
 
	// Put the Recipe data into the cell
	cell.textLabel.text = recipe.name;
	cell.imageView.image = recipe.image;
 
	return cell;
}

So it’s a little harder to set up in the AppDelegate, but it’s easier to use after that.

You only pass the data model to objects that need it. Those objects now don’t depend on any other part of the code to do their job. That’s an approach I have been using throughout this tutorial series. For example, when we open the Recipe Details screen, we give that view controller all the data it needs to use and nothing more. The view controller is completely self-contained; it does not depend on any other classes or global variables to do its job. Once we’ve created it and filled in its properties, it is good to go.

We could put the Recipe Details screen in a totally different part of the app if we wished, as long as we have a DataModel and Recipe object to give it. You should strive to design your view controllers (and any other classes) that way, with a little dependencies on external state as possible.

You can find the source code for the final solution in Version 6C.

Our class diagram now looks like this:

Figure 6.1. The DataModel is now shared among the view controllers.

Figure 6.1. The DataModel is now shared among the view controllers.

In this chapter, we hid the fact that favorites are stored with NSUserDefaults. If we want to change how we store the favorites — maybe a PList file or JSON would be better – then we can do that without having to change anything besides DataModel. It is DataModel‘s responsibility (and DataModel alone) to store and retrieve this data. None of your other classes should have to care about that. All they care about is that they can ask the DataModel for how many favorites there are, and what they are. It’s up to DataModel to make sure it does that properly, but how it does that is an implementation detail as far as anyone else is concerned.

Note: We could have stored the fact whether a recipe is a favorite or not directly into the Recipe object instead of DataModel. Sometimes it’s obvious where a piece of data or logic goes, but often it’s a matter of taste and personal preference. There is usually more than one way to do it. And if you find in the course of developing your app that your initial choice is no longer the best choice, then ruthlessly refactor your code until its structure is good again. It doesn’t really matter to me, as long as your code is clean!

In part 3 of this tutorial, we’ll finish the app.

Comments

  1. Bruno says:

    This is nice stuff.
    It’s nice to see that I am doing almost everything like you do, I started with objective-c this year and I never thought that I could define a property without defining a variable like you did in the final example (defining the dataModel to the other viewControllers), this changes everything again.

    Great work.

  2. Daniel says:

    Thank you!!! This tutorial is better than most chapters in iPhone development books.

  3. valshebnik says:

    Thank you for the very interesting tutorial, it’s very enjoyable to read and think.
    I don’t get why using a singleton can be considered as a bad practice. This is not the same as a global variable, because you’re not allowed to change it, I mean that shared instance usually is read only. So, nothing can change it’s value causing the instability. At the same time it’s quite convenient to use, because in any point of the app you can talk to it and ask to do some job for you.
    In our example why it would be a bad idea to implement DataModel class as a singleton? You can even imagine we’re dealing with a huge app. To my mind it would be even simpler, because we wouldn’t have to pass DataModel instance between controllers each time.

  4. Matthijs says:

    In smaller projects, you can usually get away with singletons. They’re easy to use and, because the program is limited in complexity, will cause few problems. However, as software becomes larger you want to avoid global state as much as possible, especially for data model objects.

  5. Andrew says:

    I decided to christen a noobish blog of mine with a link and description of these tutorials of yours: http://andrew.fm/development/2011/08/excellent-ios-tutorials-from-mr-hollemans/

    I have a quick question. You use “@class” at the top of some of your files. Could you also use “#import” for the class instead? I’m guessing that all you need to do is tell the .h that the class exists, without actually using the class’s methods, so that’s why @class suffices, while #import is for when you need to actually use the class. is that right? And since a .h doesn’t call methods, it’s not necessary to #import it.

  6. Matthijs says:

    Thanks for the link, Andrew! You’re right about @class. I prefer to use that when a full #import isn’t really required. If your projects grow larger, limiting the number of #import statements will speed up compiles. It also helps with circular dependencies: if A.h #imports B.h, then B.h can’t import A.h but it can do @class A.

  7. Jay Q. says:

    Great article! I’ve learned alot through your blog posts.

    Like Bruno, I didn’t know variables are not required when creating properties. This begs the questions (at least for me), why use variables at all?

  8. Matthijs says:

    @Jay Q: Not everything has to be a property. There is also a cost associated with accessing properties as you always perform a method call. In many cases the difference doesn’t matter but in performance critical code there is a definite advantage to using variables directly instead of properties.

    Also, with the new ARC feature of iOS 5, I prefer to use properties only for data that needs to be accessible from outside of the class. For internal computations, I stick with instance variables. Before ARC, people often use properties to simplify memory management issues (because assigning a new object to a property will automatically release the previous value, etc) but with ARC that also goes for instance variables.

  9. dingwenjie says:

    I have a question about the DataModel in the project of the version 6c.
    In SmoothiesAppDelegate.h the dataModel is declared a retained property,and in .m file you use “self.dataModel =[[DataModel alloc] init] “to instantiate it,then its retain conunt would be 2, but you only release it once in dealloc method,it will not be actual free until the mainautorelease pool destroyed,right?

  10. Matthijs says:

    @dingwenjie: Whoops, that’s a bug. It should be self.dataModel = [[[DataModel alloc] init] autorelease]; Good find!

    It doesn’t really matter to the app because DataModel will always be around for as long as the app is active, but it’s still a mistake anyway. I changed it in the article.

  11. mike says:

    Great article. I still not sold on the idea of adding a DataModel property on any class that will need access to the data model. The two potential down sides:
    1. There is an inherit coupling to the DataModel. If a class no longer needs the data model, both the implementation and the interface need to change. And then you need to find out who owns the responsibility (see #2) of assigning the data model and remove that code as well.
    2. When you decide to make a class explicitly dependent on the data model, you also have to decide at what point and by whom should assign the data model. Imagine some class may be used in different contexts. Each of those “owners” now needs to remember to pass on the data model (if they have it) after creation. One more item for the software engineer to remember.

    Great article showing all the choices. Just wanted to show that there really is no magic bullet. With every choice there are +/- that need to be considered.

  12. Martin says:

    Hello.
    What is your opinion on the App Delegate setting the data models for the Smoothies View Controller and the Favourties View Controller, as is designed here.
    Compared with the App Delegate setting the data model on its root container and then letting the root container set the data model for the Smoothies View Controller and Favourites View Controller.
    The first method means the Delegate knows about controllers which are ‘further removed’ from it and would have to be updated if a Least Favourites View Controller/Tab were added. While the second method isolates the Delegate from knowledge of these other Controllers, but with the downside that a data model must be propegated down through them.

  13. Matthijs says:

    @Martin The root view controller in this case is the UITabBarController. You shouldn’t subclass these standard container controllers so it doesn’t make much sense to give this tab bar controller the data model object. As far as the App Delegate is concerned, there are two true “top-level” controllers: the Smoothies View Controller and the Favorites View Controller. Both need to get a reference to the DataModel object. I don’t see how else you would want to do this.

  14. Cheese says:

    Its not immediately obvious a first reading, but for the design you reject as being ugly it seems to me your reason for not liking it is not the delegate being the owner of the model (because it is the owner in the eventual design) so it must be the fact that the model is being accessed via the delegate.
    But you don’t actually state why you think it is ugly.
    So if the ugliness is accessing the model via the delegate does this mean you don’t like accessing anything in the delegate from other code? If the ugliness isn’t this, then what exactly do you not like about your rejected solution?

  15. Matthijs says:

    @Cheese It’s fine if the app delegate is the owner of the model. After all, it’s also the owner of the UIWindow and a bunch of other things. However, sprinkling your code with calls to the app delegate in order to access the model (or any other object) is just nasty. It looks ugly in the code — which is often a good indicator that it’s bad design also — and it creates a dependency from all your view controllers back to the app delegate. I very much prefer that “parent” objects pass any data along to their “child” objects, rather than having the child ask the parent (or the parent’s parent, and so on) for that data.

  16. Michael says:

    I’m a newbie to both iOS development as well as Objective-C, so please be gentle.. Though I do have years of C# OO practice, so I am picking up the syntax OK.

    After following this tutorial I can’t seem to get the data to pass or persist on my custom tableview controller.. Is there something I missing? the objects just report nil. I am using a tabbed application with a storyboard and ARC, as this template looked more fitting for the type of app i wanted to develop.

    Any suggestions? by the way, great tutorials. Much appreciated.

    Michael

  17. Matthijs says:

    @Michael How are you passing the data from one view controller to the other in your project?

  18. Michael says:

    @Matthijs Thanks for the reply, I have figured out based on another tutorial that I need to get the NavigationController from the top-level in the rootViewController in the AppDelegate. So now I am able to pass the DataModel from the delegate to my top-level window and access my data. And to answer your question, I am passing my data model class by assigning it to my datamodel property in each view.

    Now I have successfully passed my datamodel from the appdelegate to my first navigationcontroller. How do i pass it to another tab in my tabcontroller?

    Thanks for the help :)

  19. Matthijs says:

    @Michael You’ll have to ask the tab bar controller for the view controller that is shown in that tab:

    MyViewController *controller = (MyViewController *)
        [self.tabBarController.viewControllers objectAtIndex:1];
    
  20. nepalibabu says:

    Thank you it is a nice tutorial appreciate your work.

  21. Iva says:

    Matthijs,
    thank you very very much for your tutorial!!!

    (I made a version 6B in a Storyboard, when somebody needs:
    http://www.file-upload.net/download-6925147/SmoothiesStoryboard.zip.html )

  22. Iva says:

    Hallo Matthijs,
    is it possible, to delete all favorite Smoothies from the Table View on one button click (that also when I close and again open app table view is clear)?

    When I delete one row after the other (commitEditingStyle) and close and again open the app, all Smoothies stays in table view.

    Only when I tap a button favoriteTapped once again, the row with Smoothie disappear from the table view also when I close and open the app.

    Can you help me, please?
    Thank you very much!

Leave a Reply

Your email address is required but will not be visible to others.

 *
 *