14 Jun 2011 When to use Delegation, Notification, or Observation in iOS
A common problem that we often experience when developing iOS applications, is how to allow communication between our controllers, without the need to have excessive coupling. Three common patterns that appear time and time again throughout iOS applications include:
- Notifications through Notification Center, and
- Key value observing
So why do we need these patterns and when should and shouldn’t they be used?
The following discussion of the three patterns are purely from my own experiences of developing iOS applications. I’d like to discuss why I feel a particular pattern is better than another and why I believe certain patterns are better in certain circumstances. The reasons I give are not gospel, and are just my personal opinions. Please feel free to argue and provide me with reasons and experiences you’ve had.
What is it about these three patterns?
The three patterns chosen are all ways for one object to communicate events to other objects without requiring them to be coupled. They are ways for objects to “inform” the occurrence of certain events, or more accurately, ways to allow certain events to be heard by others. This is quite a common task for objects to have to do, as without communication, controllers cannot integrate into the application. Another goal of a controller however, is to be as self contained as possible. We want our controllers to be able to exist on their own, without coupling them to any other controllers above them in our controller hierarchy. Controllers can create other controllers and communicate freely with them, but what we don’t want is controllers being tied back up to their creator. If we do couple them, then we lose their ability to be reused and lose their ability to completely control an isolated component of our application.
The three patterns all provide ways for controllers (or other objects) to communicate and be useful within an application, without the need to couple them and lose their self-containment. Whilst I’ll be describing the patterns and their use in an iOS application context, it is important to note that they do exist and are used elsewhere. The comments I make may or may not apply to their use in other contexts.
When you first start out programming iOS application, you’ll most likely notice the continued use of “delegates” throughout the SDK. The delegation pattern is not a pattern specific to iOS, but depending on what programming background you’ve had, it might not be immediately obvious as to what advantages this pattern provides, and why it seems to be used so often.
The basic idea of delegation, is that a controller defines a protocol (a set of method definitions) that describe what a delegate object must do in order to be allowed to respond to a controller’s events. The protocol is a contract where the delegator says “If you want to be my delegate, then you must implement these methods”. What this does is allows the controller to call methods on it’s delegate with the knowledge that the delegate will respond to the method calls. The delegate can be of any object type, so the controller is not coupled to a particular Object, but it can still be sure that the delegate will respond when it tries to tell it things.
- Very strict syntax. All events to be heard are clearly defined in the delegate protocol.
- Compile time Warnings / Errors if a method is not implemented as it should be by a delegate.
- Protocol defined within the scope of the controller only.
- Very traceable, and easy to identify flow of control within an application.
- Ability to have multiple protocols defined by one controller, each with different delegates.
- No third party object required to maintain / monitor the communication process.
- Ability to receive a returned value from a called protocol method. This means that a delegate can help provide information back to a controller.
- Many lines of code required to define: 1. the protocol definition, 2. the delegate property in the controller, and 3. the implementation of the delegate method definitions within the delegate itself.
- Need to be careful to correctly set delegates to nil on object deallocation, failure to do so can cause memory crashes by calling methods on deallocated objects.
- Although possible, it can be difficult and the pattern does not really lend itself to have multiple delegates of the same protocol in a controller (telling multiple objects about the same event)
In iOS applications there is a concept of a “Notification Center”. It is a singleton object that allows for objects to be notified of events occurring. It allows us to satisfy the goal of communicating between a controller and an arbitrary object with a low level of coupling. The basic concept of this pattern is that a controller uses a key (notification name) in order to allow other objects to hear about special events occurring within the controller. Then unbeknown to the controller, other objects (observers) can react to the notification events by registering for notifications with the same key.
- Easy to implement, with not many lines of code.
- Can easily have multiple objects reacting to the same notification being posted.
- Controller can pass in a context (dictionary) object with custom information (userInfo) related to the notification being posted.
- No compile time to checks to ensure that notifications are correctly handled by observers.
- Required to un-register with the notification center if your previously registered object is deallocated.
- Not very traceable. Attempting to debug issues related to application flow and control can be very difficult.
- Third party object required to manage the link between controllers and observer objects.
- Notification Names, and UserInfo dictionary keys need to be known by both the observers and the controllers. If these are not defined in a common place, they can very easily become out of sync.
- No ability for the controller to get any information back from an observer after a notification is posted.
Key value observing (KVO) is a pattern in which one object can observe the value of another object’s properties to find out about changes. Where the previous two patterns (delegation and notifications) are more suited to a controller communicating with arbitrary objects, KVO is more suited to objects of any type listening for changes of another arbitrary object (not necessarily, and most often not a controller). It is a way in which we can keep our objects in sync with one another; a way in which we can make one object react when another object’s state changes. It is only used for properties and cannot be used to respond to methods or other actions.
- Can provide an easy way to sync information between two objects. For example, a model and a view.
- Allows us to respond to state changes inside objects that we did not create, and don’t have access to alter the implementations of (SKD objects).
- Can provide us with the new value and previous value of the property we are observing.
- Can use key paths to observe properties, thus nested objects can be observed.
- Complete abstraction of the object being observed, as it does not need any extra code to allow it to be observed.
- The properties we wish to observe, must be defined using strings. Thus no compile time warnings or checking occurs.
- Re-factoring of properties can leave our observation code no longer working.
- Complex “IF” statements required if an object is observing multiple values. This is because all observation code is directed through a single method.
- Need to remove the observer when it is deallocated.
Summing up the options
With these three patterns providing both pros and cons, how do we aggregate and sum these up in order to influence which pattern to use in which situation. There is no right or wrong pattern to use. Each pattern provides a way for objects to inform other objects of events, without the need for the informer to know about the listener. Of these three patterns I think Key Value Observing has the clearest use case, and has a clear suitability to a specific requirement. The other two patterns however have very similar uses, and are often used to provide communication between controllers. So which of these two should be use when?
In my personal experience of making iOS applications, I have often seen an excessive use of the Notification pattern. I personally very much dislike using the notification center. I find it is just too hard to follow the flow of your application. Keys for the UserInfo dictionaries passed around become out of sync, and too many constants need to be defined and placed in a common place. It is very hard for developers who start work on an existing project to understand the flow of an application when the notification center is used excessively.
I believe that communication between controllers should be made very clear through the use of well named protocols and well named protocol method definitions. Making the effort to define these protocol methods will yield much easier code to read, and provide much more traceability within your app. Changes to delegate protocols and implementations will be picked up by the compiler, and if not (EG if you are using selectors) your app will at least crash during development, rather than just having things not working properly. Even in scenarios where multiple controllers need to be informed of the same event, as long as your application is well structured in a controller hierarchy, messages can be passed up the hierarchy where they can be passed back down to all controllers that need to know of the events.
Of course there are exceptions where the delegation pattern just does not fit and notifications make more sense. An example might be an event that every controller in your application needs to know of. However these types of scenarios are very rare. Another example might be in scenarios whereby you are building a framework that needs to announce events to the application it is running in.
As a rule of thumb I will only use observation, for property level events within objects that I did not code, or for property level events within model objects that are tightly bound to a view object. For all other events, I will always try to use a delegate pattern, if for some reason I can’t do that, I will first assess whether I have something critically wrong with my app’s structure, and if not, only then will I use notifications.