The Motivation
I've always wondered why the Foundation and Cocoa frameworks in Objective-C are so biased towards the delegate pattern. Don't get me wrong, I don't have anything against it. But there are some cases where using the observer pattern would make more sense. Take, for instance, the NSAnimationDelegate protocol. Most of the 'delegate' methods are actually just state change notifications. Out of the five methods in this protocol only one is an actual delegate method: animation:valueForProgress:.The absence of the observer pattern, especially in the UI classes, prompted me write a reusable observable class to use in my custom UI code. Similar to Java's Observable class.
The Problem
Probably the biggest implementation difference between the observer pattern and the delegate pattern is that observable objects support multiple observers, while the delegate is just one object. But this is precisely what makes the observer pattern one of my favorites. It is also what makes it harder to implement.Also, doing a bullet-proof implementation requires handling the case of an observer being added or removed inside a notification call. The observer collection cannot mutate while iterating so it needs a clever way of handling this (for some ways of doing this look here).
The Solution
This is how the Observable class looks like:@interface Observable : NSObject - (void)addObserver:(id<NSObject>)observer; - (void)removeObserver:(id<NSObject>)observer; - (void)notifyObservers:(NSInvocation*)invocation; @endIt is pretty standard, except that you pass an NSInvocation when you want to send a notification. This is so that we have flexibility with observer protocols. If you haven't, you might want to check out the previous post: Making NSInvocations, but more on this later.
We start by creating the observer collection. Since this is Objective-C (possibly for iOS, e.g. iPhone), we need to be careful not to retain the observers to avoid circular references. So we need to create a non-retaining mutable set:
observers = (NSMutableSet*)CFSetCreateMutable(NULL, 0, NULL);The reason it is a set and not an array is to make adding and removing observers faster.
And here is how you notify the observers:
- (void)notifyObservers:(NSInvocation*)invocation {
notifying = YES;
for (id<NSObject> observer in observers) {
if (![pendingRemoves containsObject:observer] && [observer respondsToSelector:[invocation selector]]) {
[invocation setTarget:observer];
[invocation invoke];
}
}
notifying = NO;
[self commitPending];
}
The notifying flag and the commitPending method call are there to handle addition and removal of observers inside a notification. If the notifying flag is set then we don't add the observer to the main observers collection. We instead add it to a temporary collection (pendingAdds) and only in commitPending do we actually add it to the main observers collection. Here is the code:- (void)addObserver:(id<NSObject>)observer {
if (notifying) {
[pendingRemoves removeObject:observer];
[pendingAdds addObject:observer];
} else {
[observers addObject:observer];
}
}
The code for removeObserver: is very similar and the code for commitPending is straight forward.Conclusion
Let me finish by showing how you would use this. Let's say you have an animation class you want to make observable. You want to receive notification for the animation starting or stopping:@protocol AnimationObserver - (void)animationDidStart; - (void)animationDidStop; @endHere is how you would implement the Animation class:
@interface Animation : Observable
...
- (void)start;
- (void)stop;
@end
@implementation Animation
...
- (void)start {
...
NSInvocation* inv = [NSInvocation invocationWithProtocol:@protocol(AnimationObserver)
selector:@selector(animationDidStart)]
[self notifyObservers:inv];
}
- (void)stop {
...
NSInvocation* inv = [NSInvocation invocationWithProtocol:@protocol(AnimationObserver)
selector:@selector(animationDidStop)]
[self notifyObservers:inv];
}
@end
Notice how it uses the invocationWithProtocol:selector: method from the previous post, Making NSInvocations. Actually this is precisely the use case I had in mind when I implemented the NSInvocation additions. So there you have it. For more information on the observer pattern you can check out the Wikipedia entry or the GOF book. If you have questions or further improvements feel free to leave a comment.
The source code for this article, including unit tests, is available at GitHub. To compile you will need Google Toolbox for Mac.
Isn't this essentially the same thing as using NSNotification center?
ReplyDelete@Daniel: the effect is the same but I find this approach to be cleaner. Compare [myObject addObserver:self] to [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(animationDidStop) name:ANIMATION_DID_STOP_NOTIFICATION object:myObject]
ReplyDeleteWhy couldn't you use Key-Value Observing (KVO) :
ReplyDeletehttp://developer.apple.com/library/mac/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html
Key-Value observing is useful for observing changes in values, but not necessarily for observing more general changes to the state of an object. For instance, you cannot pass parameters in the key-value observing notification.
ReplyDeleteAlso, key-value observing doesn't let you even choose the selector, so you will probably end up using an 'if' statement to determine which notification event you got.
Finally, compare [myObject addObserver:self] to [myObject addObserver:self forKeyPath:@"isRunning" options:NSKeyValueObservingOptionNew context:NULL]
This is good stuff, and helpful. Thanks for posting it. A question: wouldn't wrapping a synchronized block around the accesses to the 'observers' set achieve the same thing as your notifying lock? You could keep your pending Adds and pendingRemoves if you wanted, to avoid blocking threads who wanted to add or remove an observer, but unless there's a lot going on you probably don't need it.
ReplyDeleteSomething like this:
- (void)addObserver:(id)observer {
@synchronized(observers) {
[observers addObject:observer];
}
}
@Tim: I am not using locks, in fact this code is _not_ thread safe. The reason for the notifying flag is to allow observers to be added and removed within a notification call. If you wanted to make the code thread safe you could wrap all set access inside a @synchronized block, as you say.
ReplyDeleteThe pendingAdds and pendingRemoves would be unnecessary if you simply use -allObjects to get a copy of the elements of the set.
ReplyDeleteTrue, but then you would have to copy the set every time you dispatch a notification. And the most common use case is calling add and remove a couple times and notify a bunch of times so I'd rather have notify be faster.
Delete[invocation setTarget:observer]; [invocation invoke];
ReplyDeletecan be written as
[invocation invokeWithTarget:observer];
More useful would be for observers to be able to subscribe to a specific selector, rather than all of them.
ReplyDeleteDepends or your particular observable. I usually design observables to be a cohesive whole instead a bunch of separate things. In your case you might want to look into KVO (https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html)
DeleteWould it be appropriate to use the Observer pattern in a view controller object to enable/disable menu items or options, according to the application 'business logic' ?
ReplyDeleteFor example, I'm learning Cocoa for MacOS by creating a serial terminal. My app targets engineers/hobbyists/mad scientists/etc who would like to talk with embedded systems (ex.: Arduino, Raspberry PI, God knows what other devices, etc.) via an asynchronous, serial link. Some User actions, such as configuring a serial port and starting a session with remote device, do not make sense until a serial device is selected. Also, a serial device binds to one and only one session. I think the Observer would be a clean way to implement some of the controller behaviour.
I've got some pointers on how to use the IOKit and standard POSIX functions to interface with serial devices (USB-to-Serial dongles, mostly, or the Bluetooth serial port... maybe). My app can search the I/O registry for serial devices and extract the corresponding /dev/cu.xxx device file. I'm only concerned about the best practices to implement the rest of it.
Thanks for advice,
Yves
Yes, for sure. You can create a protocol for observing configuration changes and implement the protocol in your view controller.
Delete