One of the techniques which I have found to be extremely useful are notifications in Objective-C. Using NSNotification to send broadcasts to all objects in your app is something that I did not see in any other language or framework I visited before. Here is a quick recipe to show how to use them.
Think of notifications as a method of informing all your class instances within your app at the same time. Sometimes there is a global change that does not have a single target like you would for a delegate. Without notifications you would have to keep track of all objects to be informed of the change manually, loop through them and call an update method.
To give one useful example: In MyAppSales you can set your main currency so that all amounts get converted into the currency you are used to instead of showing you Japanese Yen. Now what happens if the user chooses a different currency and you have objects like table views, table cells or your own class instances that need to change something whenever the main currency changes. Enter NSNotification and NSNotificationCenter.
You might have read my article of using a shared global instances of an engine classes to keep data that should be globally accessible. Maybe you have even restructured your code such that now all global communication goes via these so-called singletons. I consider notifications as a complementary technique to them. For singletons you KNOW that there is just one and how to access it (via class method sharedInstance). For notifications you DON’T CARE how many (if any at all) object instances are interested in the updates. It is such loose coupling that allows you to structure your code in a much more maintainable fashion.
To make use of notifications you need to do these things:
- decide on a name for the notification and what data you want to send as userInfo.
- Post a notification to the notification center inside the method that triggers it.
- Subscribe to this notification, possibly in the init of objects
- Add an unsubscription to the notification to the corresponding dealloc
- Implement a call-back method that is called when the notification arrives
The userInfo pointer in NSNotification can contain any kind of address or even value that you wish to. If you don’t need it it’s send nil. If you need to send multiple values then package them into an NSDictionary. The posting of the notification looks like this. I tied it to a property so that when the mainCurrency gets set, the notification will be sent.
#pragma mark Custom Properties - (void) setMainCurrency:(NSString *)cur { if (mainCurrency==cur) { return; } [mainCurrency release]; mainCurrency = [cur retain]; // we want all parts of the app to know if there is a new main currency, might be some table updating necessary [[NSNotificationCenter defaultCenter] postNotificationName:@"MainCurrencyChanged" object:nil userInfo:(id)mainCurrency]; } |
You can see that the first part retrieves the default notification center singleton and then the postNotificationName method is called with the notification name, object is almost ever nil (unless you only want to sent to a certain object) and as userInfo I am sending the new mainCurrency value.
Subscribing to these notifications is even easier:
// anywhere inside the subscribing class [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mainCurrencyNotification:) name:@"MainCurrencyChanged" object:nil]; |
Don’t forget the unsubscription in dealloc:
// Remove notification observer [[NSNotificationCenter defaultCenter] removeObserver:self]; |
Even if you subscribe to multiple notifications you only need this line to remove all subscriptions. And finally the implementation which has the match the selector specified in addObserver:
- (void)mainCurrencyNotification:(NSNotification *) notification { sumRoyaltiesEarned = 0; // this value is in main currency, forcing recalculation on new call } |
This implementation does not make use of the userInfo because it does not need to. Here is one example that does:
#pragma mark Notifications - (void)newAppNotification:(NSNotification *) notification { if(notification) { NSDictionary *tmpDict = [notification userInfo]; NSInteger insertionIndex = [[tmpDict objectForKey:@"InsertionIndex"] intValue]; NSIndexPath *tmpIndex = [NSIndexPath indexPathForRow:insertionIndex inSection:0]; NSArray *insertIndexPaths = [NSArray arrayWithObjects: tmpIndex, nil]; [self.tableView insertRowsAtIndexPaths:insertIndexPaths withRowAnimation:UITableViewRowAnimationRight]; } } |
You can see that there are multiple values in the NSDictionary which was passed in userInfo. I am retrieving one and use it subsequently.
That’s really all there is to it. Notifications are basically transformed into 0 to n method calls and they are executed synchronously before the runloop goes for another round. This means that it is safe to pass autoreleased objects as well as properly retained ones because it is only after your code that the runloop will empty the autorelease pool.
One final word of warning: don’t overdo it. Notifications are extremely useful but there might be scenarios where you are tempted to blast data out globally, when in reality you only have a single delegate that really is interested in the data. In these cases you should have a look at defining your own delegate protocol instead of using notifications.
Categories: Recipes
Hi Oliver,
A great article that helped me solve a problem in my new App.
Keep the good work up.
Best regards
Michael