Is it generally considered best practise to reduce the coupling or dependencies between classes in your application. A key benefit is that your code becomes easier to maintain since a change in one class is less likely to impact another dependent class. It also becomes easier to reuse classes across applications.
Cocoa implements a number of features such as delegates and notifications that make this easier to achieve though it can be confusing at first to know when you should use one or the other (or both) or these techniques.
Delegation
The concept of delegation as its name suggests allows an object to send a message to another object (the delegate) so that it can customise the handling of an event. The UITableViewController provides a common example of this in an iPhone application. The delegate of the UITableViewController is informed when a user will do something such as select a row allowing the delegate to prevent or change the action. A further call is made to the delegate when the user did do something allowing the delegate to take further action such as saving data or triggering the update of a view.
The important point about delegation is the way it is implemented allows for minimal dependency between the delegating object and its delegate. The delegating object needs to have a reference to its delegate so that it can call methods in the delegate. However this reference is declared using the anonymous id type:
id delegate;
This way the delegating object does not need to know the type of its delegate which removes a big potential dependency between the classes. This weak reference between the two classes means that the compiler cannot verify that a delegate class actually implements the methods expected by the delegating object.
To avoid unexpected runtime exceptions a delegating object can test if a delegate responds to a certain message before invoking it:
if ([delegate respondsToSelector:@selector(myMethod)]) {
[delegate myMethod];
}
This allows a delegate to optionally implement some methods. The ability to specify some delegate methods as mandatory or optional can be formally defined (and checked at compile time) through the use of protocols. There is an example on using delegates with protocols in the post on iPad Modal View Controllers so I will skip ahead to the sending and receiving of notifications.
Notification
The concept of notification differs from delegation in that it allows a message to be sent to more than one object. It is more like a broadcast rather than a straight communication between two objects. It removes dependencies between the sending and receiving object(s) by using a notification center to manage the sending and receiving of notifications. The sender does not need to know if there are any receivers registered with the notification center. There can be one, many or even no receivers of the notification registered with the notification center.
The other difference between notifications and delegates is that there is no possibility for the receiver of a notification to return a value to the sender. Remember the earlier example of a delegate method indicating that a user will select a row in a table. The delegate has the opportunity to return a value which prevents the user from selecting a row. If we use a notification to inform another object that the user is selecting a row there is no way to return a value to influence this action.
Typical uses of notifications might be to allow different objects with an application to be informed of an event such as a file download completing or a user changing an application preference. The receiver of the notification might then perform additional actions such as processing the downloaded file or updating the display.
The Default Notification Center
Cocoa allows you to create multiple notification centers but in practise on the iPhone you will probably only ever need to use the default notification center. You access this default notification center using a class method as follows:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
Naming a Notification
A notification consists of a name, an object and an optional dictionary to contain user information. The name is used to identify the notification so you should try to choose something unique to your application. Using a two or three letter prefix specific to your application is a good way to do this. Apple also recommends that you define the name as a global constant string variable rather than using the hard coded string value in your code. If you look at the way Apple defines notification names you will also see the name often follows the pattern did/will
So for example, a name to indicate that a download has finished might be defined as follows:
NSString *ABCMyApplicationDidFinishDownload = @"ABCMyApplicationDidFinishDownload";
The header file for the class that generates this notification would include the declaration:
extern NSString *ABCMyApplicationDidFinishDownload;
Posting a Notification
There are two ways to post a notification depending on whether we want to send the optional user information or not. In the simplest case without the user information you would post a notification as follows:
[[NSNotificationCenter defaultCenter]
postNotificationName:ABCMyApplicationDidFinishDownload
object:self];
The object can be any object we want to send with the notification but is usually either the object posting the object or nil. To send additional data with the notification you need to construct a dictionary to hold the data. So in our example if we want to send a filename with our message we would first create a dictionary to hold the filename.
NSString *ABCMyApplicationFilename = @"ABCMyApplicationFilename";
...
...
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:filename
forKey:ABCMyApplicationFilename];
Our dictionary key would as with the notification name be declared in the header file of the class posting the notification:
extern NSString *ABCMyApplicationFilename;
Posting the notification with the user information is now simple:
[[NSNotificationCenter defaultCenter]
postNotificationName:ABCMyApplicationDidFinishDownload
object:nil
userInfo:userInfo];
Observing a Notification
To receive a notification you need to register an observer with the notification center for the notification name you are interested in. If you are adding an observer for a view controller in an iPhone application a good place to do that would be the viewDidLoad method. This ensures that you create one and only one observer for the notification when the controller finishes loading. You register the observer as follows:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(downloadFinished:)
name:ABCMyApplicationFilename
object:nil];
The observer object in this case (self) is the view controller and the selector is used to specify the method in our controller that will be called when the notification is received (technically this is the message the notification center will send to the observer object). The method must return a void and take a single parameter as follows:
- (void)downloadFinished:(NSNotification *)notification
If you need to get at the user information sent with the notification you can extract it from the NSNotification object:
- (void)downloadFinished:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
NSString *filename = [userInfo objectForKey:ABCMyApplicationFilename];
...
}
Unregistering an Observer
An important step to remember if you register an observer for a notification is to ensure that you remove that observer when it is no longer required. For example if we register a view controller as an observer and that view controller is then deallocated the application will most likely crash if the notification center attempts to send it a notification. To ensure this does not happen we should unregister the observer in both the viewDidUnload and dealloc methods of the view controller.
You can unregister for a specific notification using the removeObserver:name:object method but it is often easier to just unregister for all notifications, especially if your controller is observing several notifications:
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self];
...
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
...
[super dealloc];
}
Performance of Notifications
At first notifications seems like a cost free way of avoiding dependencies between classes. You do not even need to add a delegate instance variable to your class. However before getting carried away with notifications it is worth being aware of the downsides. When you post a notification the notification center will deliver the message to each registered observer synchronously. Control is not returned to your code until all of the observers have had their registered method called.
This means that you need to be careful if you need to deliver the notification to many observers of if the code posting the notification is waiting to complete some action. Control is not returned to the posting code until all of the observer methods have been called and completed (the observer methods are called one after the other in some unspecified order).
One way around this is to have additional notification centers on different threads and use asynchronous notifications (NSNotificationQueue) that allow the calling code to return immediately. That is a lot of extra complexity in most cases. A simpler workaround is to delay processing of the notification using the performSelector:withObject:afterDelay: method:
- (void)downloadFinished:(NSNotification *)notification {
[self performSelector:@selector(doTheRealWork:)
withObject:notification
afterDelay:0.0f];
}
This allows the method that posted the notification to get control back faster. Note however that the observer methods are still executed on the same thread.
Delegation or Notification?
As often seems to be the case this has turned into a longer post than I expected but I think we can finally consider when to use delegation and when to use notification. One obvious difference is that delegation is for sending aone-to-one message (to which the receiver can return a value) whereas notification is a one-to-many message (where the receivers cannot return anything to the sender). This leads to some clear recommendations:
- use delegates when you want the receiving object to influence an action that will happen to the sending object.
- use notifications when you need to inform multiple objects of an event.
(There is one other situation that I should briefly mention where notifications might not be the best choice. If you want to be informed of a change to a property of an object you might be better off using the Key Value Observing protocol provided by Cocoa.)
In situations where either of these guidelines could apply there may not be a clear choice between delegation or notification. For example in situations where you just want to inform a delegate that something has happened you could also choose to use notification. Likewise if you are only sending a notification to a single object you could also choose to use delegation. One extra consideration is whether it is likely that you could have more than one observer in the future in which notification would be the best choice.