You have your business logic in Recipes:
Recipe.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#import "Recipe.h" @implementation Recipe @synthesize recipeId, name, instructions, image; - (void)dealloc { [name release]; [instructions release]; [image release]; [super dealloc]; } - (NSComparisonResult)compareName:(Recipe*)recipe { return [self.name localizedCaseInsensitiveCompare:recipe.name]; } - (NSString*)description { return [NSString stringWithFormat:@"%@ ID: %d, Name: %@", [super description], self.recipeId, self.name]; } @end |
Recipe.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#import "Recipe.h" @implementation Recipe @synthesize recipeId, name, instructions, image; - (void)dealloc { [name release]; [instructions release]; [image release]; [super dealloc]; } - (NSComparisonResult)compareName:(Recipe*)recipe { return [self.name localizedCaseInsensitiveCompare:recipe.name]; } - (NSString*)description { return [NSString stringWithFormat:@"%@ ID: %d, Name: %@", [super description], self.recipeId, self.name]; } @end |
We put our business logic into a data structure. Implement some methods that manipulate this business logic. This is called our data model.
DataModel.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
@class Recipe; /* * Notification that is sent when the list of recipes is changed. */ extern NSString* const RecipesChangedNotification; /* * Notification that is sent when the list of favorites is changed. */ extern NSString* const FavoritesChangedNotification; /* * The top-level data model object for our app. */ @interface DataModel : NSObject { int nextId; NSMutableArray* sortedFavorites; } // The favorites as a list of Recipe objects, sorted by name @property (nonatomic, readonly, retain) NSArray* sortedFavorites; // 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; // Adds a Recipe to the end of the list. - (void)addRecipe:(Recipe*)recipe; // Deletes a Recipe object from the list. - (void)removeRecipeAtIndex:(int)index; // Call this to send out a notification that the Recipe has changed - (void)didChangeRecipe:(Recipe*)recipe; // Returns the number of favorites. - (int)favoritesCount; // Determines whether a Recipe is in the list of favorites. - (BOOL)isFavorite:(Recipe*)recipe; // Adds a Recipe to the favorites. - (void)addToFavorites:(Recipe*)recipe; // Removes a Recipe from the favorites. - (void)removeFromFavorites:(Recipe*)recipe; @end |
DataModel.m – this is where we manipulate our business logic into a data structure and implement wrapper methods such as add, delete, search, indexOf…etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
#import "DataModel.h" #import "Recipe.h" static NSString* const FAVORITES_KEY = @"Favorites2"; NSString* const RecipesChangedNotification = @"Smoothies-RecipesChanged"; NSString* const FavoritesChangedNotification = @"Smoothies-FavoritesChanged"; @interface DataModel () // The list of Recipe objects @property (nonatomic, retain) NSMutableArray* recipes; // The list of favorite Recipe objects @property (nonatomic, retain) NSMutableArray* favorites; // We re-declare the property here to make it read-write instead of read-only @property (nonatomic, retain) NSArray* sortedFavorites; - (void)loadRecipes; - (Recipe*)findRecipeWithId:(int)recipeId; - (void)loadFavorites; - (void)saveFavorites; @end @implementation DataModel @synthesize recipes, favorites, sortedFavorites; - (id)init { if ((self = [super init])) { [self loadRecipes]; [self loadFavorites]; } return self; } - (void)dealloc { [recipes release]; [favorites release]; [sortedFavorites release]; [super dealloc]; } - (void)loadRecipes { // Create the recipe list object recipes = [[NSMutableArray arrayWithCapacity:10] retain]; // And add a few recipes to it Recipe* recipe = [[Recipe alloc] init]; recipe.recipeId = 1; recipe.name = @"Banana Shake"; recipe.instructions = @"1 frozen bananan1 fresh bananandollop of coconut milknhalf a glass of waternoptional: pinch of vanilla"; recipe.image = [UIImage imageNamed:@"Banana Shake.png"]; [recipes addObject:recipe]; [recipe release]; recipe = [[Recipe alloc] init]; recipe.recipeId = 2; recipe.name = @"Green Smoothie"; recipe.instructions = @"2 bananasnhalf a glass of orange juicenhandful of fresh spinach"; recipe.image = [UIImage imageNamed:@"Green Smoothie.png"]; [recipes addObject:recipe]; [recipe release]; recipe = [[Recipe alloc] init]; recipe.recipeId = 3; recipe.name = @"Raspberry-Blueberry"; recipe.instructions = @"1 bananan125 gr frozen raspberriesn125 gr frozen blueberriesn1 glass soy milknhalf a glass of water"; recipe.image = [UIImage imageNamed:@"Raspberry-Blueberry.png"]; [recipes addObject:recipe]; [recipe release]; // When the user adds a new recipe, this is the ID it will get. It is // hardcoded here, but if we were actually loading the recipes from a // file, we would determine what the highest recipeId was and then set // nextId one higher. nextId = 4; } - (int)recipeCount { return self.recipes.count; } - (Recipe*)recipeAtIndex:(int)index { return [self.recipes objectAtIndex:index]; } - (void)addRecipe:(Recipe*)recipe { recipe.recipeId = nextId++; [self.recipes addObject:recipe]; } - (void)removeRecipeAtIndex:(int)index { // Also remove the recipe from the list of favorites Recipe* recipe = [self recipeAtIndex:index]; [self removeFromFavorites:recipe]; [self.recipes removeObjectAtIndex:index]; } - (void)didChangeRecipe:(Recipe*)recipe { [[NSNotificationCenter defaultCenter] postNotificationName:RecipesChangedNotification object:self]; // If the name of the recipe was changed, the sort order of the favorites // list may be wrong, so we reload the list of favorites as well. self.sortedFavorites = nil; [[NSNotificationCenter defaultCenter] postNotificationName:FavoritesChangedNotification object:self]; } - (Recipe*)findRecipeWithId:(int)recipeId { for (Recipe* recipe in self.recipes) { if (recipe.recipeId == recipeId) return recipe; } return nil; } - (void)loadFavorites { // Create the favorites list object NSArray* array = [[NSUserDefaults standardUserDefaults] objectForKey:FAVORITES_KEY]; self.favorites = [NSMutableArray arrayWithCapacity:array.count]; for (NSNumber* number in array) { int recipeId = [number intValue]; if ([self findRecipeWithId:recipeId] != nil) [self.favorites addObject:number]; } } - (void)saveFavorites { [[NSUserDefaults standardUserDefaults] setObject:self.favorites forKey:FAVORITES_KEY]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (int)favoritesCount { return self.sortedFavorites.count; } - (NSArray*)sortedFavorites { if (sortedFavorites == nil) // lazy loading { // Add the Recipe objects to a new list self.sortedFavorites = [NSMutableArray arrayWithCapacity:favorites.count]; for (NSNumber* number in self.favorites) { int recipeId = [number intValue]; Recipe* recipe = [self findRecipeWithId:recipeId]; if (recipe != nil) { [sortedFavorites addObject:recipe]; } } // Sort the list by last name [sortedFavorites sortUsingSelector:@selector(compareName:)]; } return sortedFavorites; } - (BOOL)isFavorite:(Recipe*)recipe { return [self.favorites containsObject:[NSNumber numberWithInt:recipe.recipeId]]; } - (void)addToFavorites:(Recipe*)recipe { // Add the recipe's ID to the favorites list [self.favorites addObject:[NSNumber numberWithInt:recipe.recipeId]]; // Save the favorites to NSUserDefaults [self saveFavorites]; // We release the old sorted list and set it to nil. The next time anyone // asks for it, it will be re-created from scratch. self.sortedFavorites = nil; // Let any listeners know the list of favorites has changed [[NSNotificationCenter defaultCenter] postNotificationName:FavoritesChangedNotification object:self]; } - (void)removeFromFavorites:(Recipe*)recipe { [self.favorites removeObject:[NSNumber numberWithInt:recipe.recipeId]]; [self saveFavorites]; self.sortedFavorites = nil; [[NSNotificationCenter defaultCenter] postNotificationName:FavoritesChangedNotification object:self]; } @end |
Firing events through Notifications
Model — fire events –> Controller
Our Data Model communicates with other UIViewController by firing Notifications like this in our DataModel.m:
1 2 3 4 5 6 7 8 9 |
- (void)didChangeRecipe:(Recipe*)recipe { [[NSNotificationCenter defaultCenter] postNotificationName:RecipesChangedNotification object:self]; // If the name of the recipe was changed, the sort order of the favorites // list may be wrong, so we reload the list of favorites as well. self.sortedFavorites = nil; [[NSNotificationCenter defaultCenter] postNotificationName:FavoritesChangedNotification object:self]; } |
Thus, from our DataModel, we fire a notification called FavoritesChangedNotification or RecipesChangedNotification, which are NSStrings defined in our DataModel.h like this:
1 2 3 4 5 6 7 8 9 |
/* * Notification that is sent when the list of recipes is changed. */ extern NSString* const RecipesChangedNotification; /* * Notification that is sent when the list of favorites is changed. */ extern NSString* const FavoritesChangedNotification; |
Whoever is listening for these notifications will execute their @selector method code.
UIViewControllers listening for Notifications
In our UIViewController we set it up like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#import "EditRecipeViewController.h" @class DataModel; /* * This is the view controller that lists all smoothie recipes. */ @interface SmoothiesViewController : UITableViewController <EditRecipeDelegate> { } // We have a strong reference to the shared Data Model object @property (nonatomic, retain) IBOutlet DataModel* dataModel; - (IBAction)addRecipe; @end |
You can see we have a strong reference to the shared Data Model.
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.
Whenever we have other UIViewControllers that updates our Data Model like RecipeDetailsViewController, we make it a so-called weak reference like this:
1 2 |
// We have a weak reference to the shared Data Model object @property (nonatomic, assign) DataModel* dataModel; |
note:
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.
end note.
We then have a UITableViewController displaying the data in our dataModel variable.
What happens if our dataModel changes data? Then in our DataModel.m, these methods will fire Notification like this:
1 |
[[NSNotificationCenter defaultCenter] postNotificationName:FavoritesChangedNotification object:self]; |
So Data Models post or fire these notifications. We have our UIViewController’s listen for them.
Our UIViewControllers listen up for notifications through [NSNotificationCenter defaultCenter]’s addObserver:
First, make sure the notification strings are set from whichever data models are firing them. In our case its in DataModel.h:
1 2 |
NSString* const RecipesChangedNotification = @"Smoothies-RecipesChanged"; NSString* const FavoritesChangedNotification = @"Smoothies-FavoritesChanged"; |
then in your UIViewController’s m file, we set up NSNotificationCenter’s addObservewr:
1 2 3 4 5 6 7 8 9 10 |
- (void)viewDidLoad { //....etc // Start listening for notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(recipesChanged:) name:RecipesChangedNotification object:nil]; } |
Once we set up the observer, we implement the method that will execute whenever we receive such a notification in our UIViewController:
1 2 3 4 |
- (void)recipesChanged:(NSNotification*)notification { [self.tableView reloadData]; } |
What this means is that our UIViewController basically are listening for notifications from our data models. Once there is a notification, we update our view (in our case UITableViewControlelr) accordingly.
Also, remember to remove those observers in your dealloc:
1 2 3 4 5 6 7 8 9 |
- (void)dealloc { // Stop listening for notifications [[NSNotificationCenter defaultCenter] removeObserver:self name:RecipesChangedNotification object:nil]; [super dealloc]; } |
Controller updates Model
Controller — updates –> Model
Now in your ViewControllers we also update our DataModel through using the self.dataModel removeRecipeAtIndex method. Our users manipulate our UITableView, and the interaction calls our commitEditingStyle:forRowAtIndexPath, which then manipulates our weak reference dataModel variable. That is how Controllers update our Models.
1 2 3 4 5 6 7 8 |
- (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]; } |