Like an example in the Mediator pattern chapter (Chapter 11), air traffic needs a centralized air traffic control. There are many operators sitting inside the air controller tower watching their radar screens to make sure no mid-air collision can occur. At the same time, pilots who are flying their multimillion-dollar big birds need to know what's happening around them, i.e., the traffic in the air. Pilots can tune to specific channels on their radio and listen to (observe) the surrounding traffic. If a traffic controller broadcasts certain precautions and warnings to those pilots who are observing the channel, the pilots can acknowledge the messages with certain actions. So in this model, anyone can know what traffic control they are observing but not the other way around (at least the air traffic controller doesn't know all observers but certain blips on the radar screen). Anyone (including real pilots) can tune in and out whenever they want without disturbing the others.
We have brought this idea to object-oriented software design to decouple objects that have different behaviors (or to extend the behavior of an object with other different ones). With this model, different objects can work together, and at the same time they could be reused in other places as well. We call it the Observer pattern.
In this chapter, we are going to discuss the concepts of the pattern as well as how it was adapted in the Cocoa Touch framework. The Observer pattern is also part of the MVC (Model-View-Controller) pattern. We will use the TouchPainter app example from Chapter 2 to discuss how to use the pattern to let a CanvasView
reflect data changes of strokes that are stored in the Model living in CanvasViewController
.
The Observer pattern is also known as the Publish-Subscribe pattern. As its a.k.a. implies, it's just like a magazine subscription. When you order a subscription from a magazine publisher, you provide the publisher your name and mailing address so the new issue can find its own way to your hand. The publisher makes sure the right magazine will be delivered to the right address. You will never (supposedly) get what you have not subscribed to. This is exactly how the Observer pattern works. An observer registers (subscribes) itself for particular notifications (magazines) with a notifier (publisher). The observer only gets what it has subscribed to when it's available from the notifier. Their static relationships are illustrated in Figure 12-1.
The Observer pattern is a publish-subscribe model. Observers
subscribe to notifications from the Subject
. ConcreteObservers
implement an abstract Observer
and override its update
method. Once an instance of Subject
needs to notify Observers
for any new changes, the Subject
will send an update
message to notify any registered Observers
stored in an internal list. Inside an actual implementation of the update
method of a ConcreteObserver
, the internal state of the Subject
can be retrieved and processed later.
THE OBSERVER PATTERN: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.[10]
The whole idea of the publish-subscribe mechanism is pretty straightforward and quite easy to understand. The Subject
provides methods to register and unregister any object that implements the Observer
protocol and is interested in handling the update
message call. When there is a change in an instance of Subject
, it will send a notify
message to itself. The notify
method contains an algorithm that defines how to broadcast update
messages to all registered observers. A common notify-update sequence at runtime is illustrated as a sequence diagram in Figure 12-2.
Figure 12-2. A sequence diagram shows the interaction between aConcreteSubject
and other concrete observers that observe updates from the aConcreteSubject
object.
aConcreteObserver
first modifies the state of aConcreteSubject
. Since there is a change in its internal state, aConcreteSubject
sends itself a notify message so it broadcasts update messages to a set of aConcreteObservers
. aConcreteObserver
and anotherConcreteObserver
receive the message and send a getState
message to aConcreteSubject
to retrieve its internal state for further processing.
One of the most obvious benefits of using the Observer pattern is that it can extend the behavior of the Subject
with N number of Observers
that have specific implementation to process the information stored in the Subject
. It's also a very important pattern for decoupling among different objects. The Cocoa Touch framework has provided developers some classes to use the pattern without writing their own.
You'd naturally think about using the pattern when
You have two types of abstraction that are dependent on each other. Encapsulating them in separate objects allows you to vary and reuse them independently.
A change in one object requires changing others, and the number of objects that need to be changed can vary.
An object needs to notify other objects without knowing what they are.
In earlier chapters, we discussed how the Model-View-Controller pattern (MVC) is a compound of different types of design patterns. Observer is one of them. A view will be associated with a controller for some particular events to occur that would affect the presentation of an application. For example, when the user clicks a "sort" button on the view, then it's passed to the controller to tell the model to sort its data in the back end. When the model successfully performs the operation on the data, it will notify all the associated controllers to update their views with the new data.
By using the Observer pattern in a MVC pattern, each component can be reused and extended independently without much interference with others in the relationships. The achieved high reusability and extensibility could not be achieved with all their logic put together in one class. So extra views can be added to a controller without modifying existing design and code. Likewise, different controllers can use the same model without changing other controllers that are using it. Especially for the model, multiple objects (either local or remote) can modify its internal data. So the model will broadcast specific changes to all observing controllers, and they will, in turn, tell their views to update the presentation with new information from the model. We will discuss more about this update mechanism in later sections.
The Cocoa Touch framework has adapted the Observer pattern with two technologies, Notifications and Key-Value Observing. Despite being two different Cocoa technologies, both of them implement the Observer pattern. We are going to discuss each of their characteristics and the differences between them.
The Cocoa Touch framework implements the one-to-many, publish-subscribe model with NSNotificationCenter
and NSNotification
objects. They allow the subject and its observers to communicate in a loosely coupled manner. The communication can take place between them without either needing to know much about the other.
The subject that wants to notify other objects needs to create a notification object, which is identifiable by a global notification name, and post it to a notification center. The notification center determines the observers of a particular notification and sends the notification to them via a message. When the objects subscribe to a particular type of notification, they need to provide the name of a method identified by a selector. The method must conform to a certain single-parameter signature. The parameter of the method is the notification object, which contains the notification name, the observed object, and a dictionary containing any supplemental information. When a notification arrives, the method will be invoked.
A model object can post a notification to the notification center after the model's internal data is changed so the message can be broadcast to other observing objects and they can respond appropriately. The model can construct a notification and post it to the notification center as follows:
NSNotification *notification = [NSNotification notificationWithName:@"data changes" object:self]; NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter postNotification:notification];
An instance of notification can be created by a class factory method (see the Factory Method pattern, Chapter 4) of NSNotification
class with a notification name and any object passed as a parameter to observers. In the foregoing example, the notification name is @"data changes"
. The exact name is implementation specific. If the subject wanted to pass itself as an object parameter, self
can be used for that purpose during the creation process.
Once the notification is created, it will be posted to the notification center as a parameter to a [notificationCenter postNotification:notification]
message call. A reference to an instance of NSNotificationCenter
can be obtained by sending a defaultCenter
message to the NSNotificationCenter
class. There is only one default notification center per process, which makes the default NSNotificationCenter
a singleton object (see the Singleton pattern, Chapter 7). defaultCenter
is a factory method that returns the sole default instance of NSNotificationCenter
for the application.
Any object that wants to subscribe to the notification needs to sign itself up for it in the first place. The following code snippet illustrates that:
[notificationCenter addObserver:self selector:@selector(update:) name:@"data changes" object:subject];
The notificationCenter
is obtained the same way as in the posting steps performed by the subject. To register an observer, an observing object needs to register self
as an observer in an addObserver:
message call. It also needs to provide a selector that identifies a method that will be invoked when the notification center notifies the observing object. The observing object can also optionally set the name of a particular notification of interest as well as any other object as a parameter for the method that will be invoked when notified. The provided information will be used by the notification center to determine what notifications should be delivered to the observing object. For the sake of our example, at least it needs to provide the same notification name in order to be able to receive the same notification.
Cocoa (Cocoa Touch as well) provides a mechanism called key-value observing with which objects can be notified of changes to specific properties of other objects. The mechanism is particularly important in the context of the Model-View-Controller pattern, as it enables view objects to observe changes in model objects via the controller layer.
The mechanism is based on the NSKeyValueObserving
informal protocol with which Cocoa provides an automatic property-observing capability to all complying objects. For automatic observing, objects that participate in key-value observing, or KVO, require compliance with the requirements of key-value coding (KVC) and complying accessor methods. KVC is based on a related informal protocol for automatic observing by accessing object properties (for detailed specifications on key-value coding compliance, please read the "Key-Value Coding Programming Guide" available on the iOS developer's website. You can also implement manual observer notifications using the methods of NSKeyValueObserving
and associated categories. With the manual implementation, you can either disable the default automatic notifications or keep both of them.
Both notifications and key-value observing are Cocoa's adaptation of the Observer pattern. Despite both relying on a similar kind of publisher-subscriber relationship, they are designed for different solutions. Table 12-1 summarizes their key differences:
Table 12-1. The Key Differences Between Notifications and Key-Value Observing
Notifications | Key-Value Observing |
---|---|
A central object that provides change notifications for all observers | The observed object directly transmits notifications to observers. |
Mainly concerned with program events in a broad sense | Tied to the values of specific object properties |
It's not rocket science to use key-value observing to activate the true spirit of the Observer pattern, especially in the context of the Model-View-Controller pattern. However, the topic itself can be a chapter of its own. If you want to know more about that, you can go the iOS developer's web site to check out its "Key-Value Observing Programming Guide," "Key-Value Coding Programming Guide," as well as "Cocoa Bindings Programming Topics."
Let's see how to use KVO to propagate notifications of changes in a model to a view (via a controller) with our old and faithful app, TouchPainter.
The canvas part of the TouchPainter app that allows the user to scribble is the heart and soul of the app. We briefly cover the components involved in the previous chapters. From a bird's eye view, it is basically a Model-View-Controller structure: Scribble
as Model, CanvasView
as View, and CanvasViewController
as Controller. The CanvasView
forwards touches to the CanvasViewController
through a responder chain (Chain of Responsibility pattern, Chapter 17). Then the CanvasViewController
processes the information of the touches and instructs Scribble
to update its internal structure with the information of the new touches. Once the Scribble
has updated its internal state (Mark
composite; please see the Composite pattern in Chapter 13 for more details), then it will notify any observer that is registered for an update notification. In this case, the CanvasViewController
is the only observer that would like to receive any updates from the Scribble
. Finally the CanvasViewController
will notify the CanvasView
to update its presentation. Their relationships are illustrated in a diagram in Figure 12-3.
Figure 12-3. An action-flow diagram shows how CanvasView
, CanvasViewController
, and Scribble
interact with each other.
We will implement the solution with the Key-Value Observing mechanism, or KVO, with NSKeyValueObserving
protocol. Luckily, NSObject
has implemented that for us so we don't need to create everything from scratch. A class diagram that shows a possible structure is illustrated in Figure 12-4.
Figure 12-4. A class diagram shows a static relationship between Scribble
and CanvasViewController
through the Key-Value Observing mechanism implemented by NSObject
.
The class diagram looks a little scary, doesn't it? For the sake of brevity, other unrelated methods are omitted in the diagram. Because we are using KVO to implement the pattern and a lot of bells and whistles are hidden from view, the class diagram in Figure 12-4 doesn't quite reconcile with the one shown in Figure 12-1. But there are two key methods in NSObject
shown in the figure that will ring a bell. addObserver:forKeyPath: options:context:
allows an object to be added as an observer to a receiver. The observer needs to override observeValueForKeyPath:ofObject:change:context:
to receive any callback from changes in the subject and process any returned value. In the previous sections, we have mentioned that the KVO update mechanism is usually automatic whenever a key-matching property is updated through its accessor method (i.e., set<key> method). If we want to manually trigger the KVO update broadcast, then we need to use willChangeValueForKey:
and didChangeValueForKey:
to wrap the changes before and after they occur.
Every time a different instance of Scribble
is assigned to an instance of CanvasViewController
, it will add itself as an observer to the instance of Scribble
with the addObserver:forKeyPath:options:context:
method. If there are changes to the internal state of the instance of Scribble
(adding / removing components in its Mark
composite object), we need to manually trigger the universal update by a pair of wrapping calls of willChangeValueForKey:@"mark"
and didChangeValueForKey:@"mark"
. The KVO implementation in NSObject
will forward a message of observeValueForKeyPath:ofObject:change:context:
to the observer CanvasViewController
. In the callback method, it will update its canvasView_
with a new instance of Mark
from the message and ask it to redraw itself. The class declaration of Scribble
is shown in Listing 12-1.
Example 12-1. A Class Declaration of Scribble in Scribble.h
#import "Mark.h" @interface Scribble : NSObject { @private id <Mark> parentMark_; } // methods for Mark management - (void) addMark:(id <Mark>)aMark shouldAddToPreviousMark:(BOOL)shouldAddToPreviousMark; - (void) removeMark:(id <Mark>)aMark; @end
Scribble
keeps an instance of Mark
as its internal state so it won't expose to other objects. Clients can send Scribble
a message of addMark:(id <Mark>)aMark shouldAddToPreviousMark:(BOOL)shouldAddToPreviousMark
to insert any Mark
instance to its internal composite. The BOOL
parameter shouldAddToPreviousMark
tells the method whether the input Mark
object should be attached to the previous one as part of its aggregation (for example, a Vertex
in a Stroke
). removeMark:(id <Mark>)aMark
allows a removal of aMark
from the parent Mark
.
A message of either addMark:
or removeMark:
for an instance of Scribble
will trigger an update broadcast to its observers. Listing 12-2 shows how they are implemented.
Example 12-2. An Implemenation of Scribble in Scribble.m
#import "Scribble.h" #import "Stroke.h" // A private category for Scribble // that contains a mark property available // only to its objects @interface Scribble () @property (nonatomic, retain) id <Mark> mark; @end @implementation Scribble @synthesize mark=parentMark_; - (id) init
{ if (self = [super init]) { // the parent should be a composite // object (i.e. Stroke) parentMark_ = [[Stroke alloc] init]; } return self; } #pragma mark - #pragma mark Methods for Mark management - (void) addMark:(id <Mark>)aMark shouldAddToPreviousMark:(BOOL)shouldAddToPreviousMark { // manual KVO invocation [self willChangeValueForKey:@"mark"]; // if the flag is set to YES // then add this aMark to the // *PREVIOUS* Mark as part of an // aggregate. // Based on our design, it's supposed // to be the last child of the main // parent if (shouldAddToPreviousMark) { [[parentMark_ lastChild] addMark:aMark]; } // otherwise attach it to the parent else { [parentMark_ addMark:aMark]; } // manual KVO invocation [self didChangeValueForKey:@"mark"]; } - (void) removeMark:(id <Mark>)aMark { // do nothing if aMark is the parent if (aMark == parentMark_) return; // manual KVO invocation [self willChangeValueForKey:@"mark"]; [parentMark_ removeMark:aMark]; // manual KVO invocation [self didChangeValueForKey:@"mark"]; } - (void) dealloc {
[parentMark_ release]; [super dealloc]; } @end
@"mark"
is the only key in the KVO in Scribble
. Normally speaking, if we want to modify its internal mark
, then we can use one of its accessor methods (e.g., setMark:
). In that case, we don't need to do anything extra to broadcast an update once a modification is done as it's being handled by the default KVO mechanism defined in NSObject
. We definitely need manual update because Scribble
allows part-whole modifications to its mark
but not just a complete replacement of it through its set
method. In fact, we want both automatic and manual updates. So we will keep the original mechanism offered by NSObject
and add a couple lines of code around where actual modifications to mark occur in the methods. In the addMark:
method, there is an if
block that tells whether the incoming aMark
needs to be added to a parent or the last Mark
. In either case, there will be a change to the internal mark, so we wrap the whole block with two statements: [self willChangeValueForKey:@"mark"]
and [self didChangeValueForKey:@"mark"]
. The first statement tells NSObject
to keep the old value of the mark while the second one keeps the new one. Either one or both of the values will be passed to observers, depending on what they want to get from an update.
How should an observer handle an update? It's not the business of Scribble
anymore once it hands off a whole bunch of update messages. Let's see how CanvasViewController
can handle an update message to update its canvasView_
with code snippets shown in Listings 12-3 and 12-4.
Example 12-3. A Class Definition of CanvasViewController in CanvasViewController.h
#import <UIKit/UIKit.h> #import "Scribble.h" #import "CanvasView.h" #import "CanvasViewGenerator.h" @interface CanvasViewController : UIViewController { @private Scribble *scribble_; CanvasView *canvasView_; CGPoint startPoint_; UIColor *strokeColor_; CGFloat strokeSize_; } @property (nonatomic, retain) CanvasView *canvasView; @property (nonatomic, retain) Scribble *scribble; @property (nonatomic, retain) UIColor *strokeColor; @property (nonatomic, assign) CGFloat strokeSize; @end
We are reusing pretty much the same CanvasViewController
that appears in other chapters. Scribble
is served as a model (as in Model-View-Controller) that helps store the user's stroke and dot information. A member instance of Scribble
sits squarely inside a CanvasViewController
object just for the purpose.
CanvasViewController
also manages an instance of CanvasView
as a member variable canvasView_
. It uses an instance of Mark
to do that actual drawing on the screen.
The implementation code for the CanvasViewController
is quite long, so I will break it into multiple parts to discuss. We are going to get the setup part of the controller, as in Listing 12-4.
Example 12-4. An Implementation of CanvasViewController
Defined in CanvasViewController.m
#import "CanvasViewController.h" #import "Dot.h" #import "Stroke.h" @implementation CanvasViewController @synthesize canvasView=canvasView_; @synthesize scribble=scribble_; @synthesize strokeColor=strokeColor_; @synthesize strokeSize=strokeSize_; // hook up everything with a new Scribble instance - (void) setScribble:(Scribble *)aScribble { if (scribble_ != aScribble) { [scribble_ autorelease]; scribble_ = [aScribble retain]; // add itself to the scribble as // an observer for any changes to // its internal state - mark [scribble_ addObserver:self forKeyPath:@"mark" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; } } // Implement viewDidLoad to do additional setup after loading the view, // typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; // ... // Setup a default canvas view // but we snip that part for brevity // ... // initialize a Scribble model Scribble *scribble = [[[Scribble alloc] init] autorelease];
[self setScribble:scribble]; // other setups... } - (void)dealloc { [canvasView_ release]; [scribble_ release]; [super dealloc]; }
Because CanvasViewController
's scribble
property is synthesized automatically, we normally don't need to provide any custom accessor methods for it. But here is the thing: CanvasViewController
is depending on any update notifications sent from scribble_
in order to further instruct how its canvasView_
(re)draws any mark
in scribble_
. It needs to add itself to its private member variable scribble_
as an observer with a message call:
[scribble_ addObserver:self forKeyPath:@"mark" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil];
The NSKeyValueObservingOptionInitial
option tells scribble_
to notify CanvasViewController
right after this message call to provide the initial value for its mark
property. That option is important because CanvasViewController
also needs to be notified when a Scribble
object is being initialized in its init*
method and the mark
property is being set for the first time. NSKeyValueObservingOptionNew
instructs scribble_
to notifiy CanvasViewController
every time when the mark
property of scribble_
is set with a new value. The context
parameter provides an optional object as a parameter for a notification. Ok, let's get back to the message call itself. The question is, where should we put it in? If it's only in the viewDidLoad
method, then when a client assigns a different Scribble
instance to the controller, the observing link will be broken. So the best place to establish a link between them is in a set accessor method for scribble_
. It serves as a gateway to prevent any possibility of breaking the observing link. Also, after a different Scribble
reference is assigned to the CanvasViewController
, the accessor method will send a message to canvasView_
to redraw itself with the mark
in a new Scribble
. Details on how canvasView_
draws a complete Mark
composite on the screen are discussed in Chapter 15.
So instead of putting the part where we set up an observing link between CanvasViewController
and its scribble_
in the viewDidLoad
method, we create the first-ever instance of Scribble
there every time the controller is loaded and assign it with the accessor method.
CanvasViewController
and its scribble_
are now hooked up, and we are ready to see some actions on updating scribble_
with touches on the canvasView_
. We put some touch event handlers in CanvasViewController
, as in Listing 12-5.
Example 12-5. Touch Event Handlers in CanvasViewController
#pragma mark - #pragma mark Touch Event Handlers - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { startPoint_ = [[touches anyObject] locationInView:canvasView_]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint lastPoint = [[touches anyObject] previousLocationInView:canvasView_]; // add a new stroke to scribble // if this is indeed a drag from // a finger if (CGPointEqualToPoint(lastPoint, startPoint_)) { id <Mark> newStroke = [[[Stroke alloc] init] autorelease]; [newStroke setColor:strokeColor_]; [newStroke setSize:strokeSize_]; [scribble_ addMark:newStroke shouldAddToPreviousMark:NO]; } // add the current touch as another vertex to the // temp stroke CGPoint thisPoint = [[touches anyObject] locationInView:canvasView_]; Vertex *vertex = [[[Vertex alloc] initWithLocation:thisPoint] autorelease]; [scribble_ addMark:vertex shouldAddToPreviousMark:YES]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { CGPoint lastPoint = [[touches anyObject] previousLocationInView:canvasView_]; CGPoint thisPoint = [[touches anyObject] locationInView:canvasView_]; // if the touch never moves (stays at the same spot until lifed now) // just add a dot to an existing stroke composite // otherwise add it to the temp stroke as the last vertex if (CGPointEqualToPoint(lastPoint, thisPoint)) { Dot *singleDot = [[[Dot alloc] initWithLocation:thisPoint] autorelease]; [singleDot setColor:strokeColor_]; [singleDot setSize:strokeSize_]; [scribble_ addMark:singleDot shouldAddToPreviousMark:NO]; } // reset the start point here startPoint_ = CGPointZero; // if this is the last point of stroke
// don't bother to draw it as the user // won't tell the difference } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { // we don't actually need to // reset the start point here // but just in case startPoint_ = CGPointZero; }
Please see Chapters 2, 13, and 15 for detailed discussions on the requirements for the drawing part of the app.
We have a whole chunk of code for handling touch events. Our goal there is to create two different drawing scenarios. It'll be a single dot if the finger lifts off at the same spot where it first touched or a stroke (line) if it is a drag with the finger. The majority of the algorithm that deals with those conditions is in the touchesMoved:
and touchesEnded:
methods. In the touchesMoved:
method, it will create a new instance of Stroke
if it is the second point of the drag and assign it to the scribble_
with a message call, [scribble_ addMark:vertex shouldAddToPreviousMark:NO]
. Further touches along the drag will be added as instances of Vertex
to a previously added Mark
(as a form of Stroke)
with the same type of message call to scribble_
. But this time the shouldAddToPreviousMark
parameter is set to YES
.
If it's a single touch from a finger, then touchesEnded:
will create a new instance of Dot
as singleDot
and ask scribble_
to add it to its internal structure with shouldAddToPreviousMark
set to NO
.
scribble_
notifies its observers (maintained by NSObject
) in every addMark:
message call. We know that the CanvasViewController
was hooked up with scribble_
with the addObserver:
message a moment ago. But it also needs to define a callback method so when an update notification comes in from scribble_
, it will do something meaningful. Part of the KVO deal requires observers to override at least one observing method. For this example, we need CanvasViewController
to listen to an observeValueForKeyPath:
message, as in Listing 12-6.
Example 12-6. An Implementation of updateWithScribble:
Method in CanvasViewController
to Handle Any Update Messages Sent from an Instance of Scribble
in Case of Data Changes
#pragma mark - #pragma mark Scribble observer method - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isKindOfClass:[Scribble class]] && [keyPath isEqualToString:@"mark"]) { id <Mark> mark = [change objectForKey:NSKeyValueChangeNewKey]; [canvasView_ setMark:mark];
[canvasView_ setNeedsDisplay]; } }
It first double-checks to make sure an update message is from an instance of Scribble
and for its @"mark"
state. If so, it will retrieve a new Mark
from a change
dictionary by sending it [change objectForKey:NSKeyValueChangeNewKey]
. The value that is associated with NSKeyValueChangeNewKey
in the dictionary will be the new value from the mark
property in scribble_
. After that, it tells canvasView_
to draw itself with the new Mark
.
We've covered some code to show a possible way to implement the Observer pattern for the TouchPainter app. At this point, you may wonder why we needed to let the CanvasViewController
update the Scribble
with touch information and at the same time, expect a notification from the Scribble
, and then tell the CanvasView
to update itself. It seems to us there are a few extra steps for something that can be done in an easier way. If you think so, then you should go back and look at the Scribble
code again. In Scribble
, there are two methods that allow modifications to its internal Mark
composite reference. One is to add a new Mark
to a parent Mark
and the other one is to remove a Mark
in the parent. We already knew that the CanvasViewController
would use the addMark:
method to add any Dot
and Stroke
to the Scribble
. But how about removing a Mark
from it then? Who will use that one? We didn't put any code in CanvasViewController
to remove a Mark
in scribble_
. That operation would most probably be invoked by another entity—for example, a command object (see the Command pattern, Chapter 20). An undo/redo operation can modify scribble_
without CanvasViewController
's knowledge. In that case, CanvasViewController
needs to know what's going on with its scribble_
behind its back. Another type of aspect that could also modify scribble_
outside the knowledge of CanvasViewController
is when the model (again as in Model-View-Controller) needs to be shared with other objects just like what databases do. A database can be read and modified by objects sitting in different processes or even across the network. Changes made by one object could be vital if they are important to other objects that are supposed to be in sync but are not.
If we implemented the same thing with NSNotification
and NSNotificationCenter
that we have discussed in one of the previous sections instead of using KVO, then here is what's going to happen:
We need to define a common identifier for both the subject and observers (scribble_
and CanvasViewController
).
When there is a change to scribble_
's internal state, then it will post a notification with the specified identifier and pass any necessary object as a parameter (as an instance of NSNotification
) to NSNotificationCenter
.
NSNotificationCenter
will in turn distribute the message to all registered observers that have subscribed to any notification tagged with the identifier.
Then the observers will process the notification in its selector that was provided to NSNotification
as a callback function.
Besides using those two approaches provided by the framework, some of you may be tempted to homebrew your own Observer infrastructure from the ground up.
In this chapter, we have covered the background information on the Observer pattern and its uses. We have also explored how we can implement it in the TouchPainter app for its Model-View-Controller architecture as a mechanism to reflect stroke changes on the screen as the user draws with a finger.
One can also take advantage of the canned implementation of the pattern with Key-Value Observing (KVO) as well as NSNotification
and NSNotificationCenter
objects in the Cocoa Touch framework without implementing the whole solution from scratch.
In the next part, we are going to see some design patterns that are useful for forming structures for abstract collections and other patterns that are directly related to their behaviors.
[10] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
18.118.141.27