Memento is defined as "an object kept as a reminder or souvenir of a person or event." That really reminds me of "sticky notes." I don't find sticky notes as useful as my buddy does. He keeps track of everything on sticky notes on his desk in his office. They are really short and brief reminders sometimes written in scribbles that only the original author, himself, can understand. Other people, myself included, see them as a bunch of junk flyers on a bulletin board. They just don't mean anything to anybody except the original author. Whenever we need a small, simple, and compact reminder, we write it on a sticky note. After that piece of information is reviewed later and the reminder is no longer valid, we toss it out in a trashcan and forget about it (it's not environmentally friendly at all, I know).
We borrow a similar idea to save the state of an object and restore it later. The state itself is created as a form of object (sticky note). It encapsulates the internal state of the original object (scribbles created by the author). Only the original object that creates the sticky note can understand the saved state and restore the original state with it. A design pattern that is elaborated from the idea is called the Memento pattern.
An application needs to save its own state in some events, such as when the user is saving a document or when the program is exiting. For example, a game may need to save the current state of the current session, such as the game level, number of enemies, types of weapons available, etc. before the game quits. When the game is reopened, the players can pick up where they left off. A lot of times, we really don't need anything super fancy to save the state of the program. Any efficient, simple method should do, but at the same time the saved information should be meaningful only to the original program. The original program should be the only entity that can understand how to decode the saved information in an archive it created. This is how the Memento pattern fits in a software design for programs like games, word processors, or other programs that need to save the snapshot of the complex state of the current context and restore it later.
THE MEMENTO PATTERN: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.[21]
There are three key roles in the pattern: originator, memento, and a caretaker. The idea is very simple. The originator creates a memento that contains its state and passes it over to the caretaker. The caretaker doesn't know how to interact with the memento but does keep it in a safe place. Their static relationships are illustrated in a class diagram in Figure 23-1.
When a caretaker asks an Originator
object to save its state, the Originator
object will create a new instance of a Memento
object with its internal state. Then the caretaker holds the Memento
object for a time or saves it in the file system, and passes it back to the Originator
object. The Originator
object has no idea how the Memento
object is going to be saved. The caretaker doesn't know what's in the Memento
object either. A sequence diagram in Figure 23-2 illustrates their interaction.
The key for this design is to maintain the privacy of a Memento
object, so only an Originator
object can access the internal state stored in the Memento
object (i.e., the Originator
's previous internal state). The Memento
class should have two interfaces: a wide one for Originator
s and a narrow one for other objects. In previous diagrams, the setState:
, state
, and init
methods should be private so other objects can't use them, except Originator
and Memento
.
You'd think about using the pattern when all of the following applies:
You need to save the object's state as a snapshot or a portion of it, which can be restored later.
You need to hide the interface with which obtaining the state would expose the implantation details.
I hope you still remember our faithful little app, TouchPainter. One of the features that we needed to put in is to save a currently active scribble on the CanvasView
in the file system. That should be easy. We can just call NSKeyedUnarchiver
's class method, archivedDataWithRootObject:
, with a Mark
composite object as a parameter (NSKeyedUnarchiver
is discussed in the "The Memento Pattern in the Cocoa Touch Framework" section). Then we can save a returned NSData
object in the file system. Here is a problem: how does a Mark
object know where (a path) it should save itself in the file system in the first place? If we really need the Mark
know about it, then we need to go back to our Composite pattern chapter (Chapter 13) and make that change. Every time we make any change to our Mark
, all of its implementing classes are also affected. Also, we are talking about saving a complete Mark
composite structure, not just the individual nodes, as well as other issues like, how can we save a portion of the composite (i.e., some nodes but not all of them)? And how can the composite know where to put the saved nodes back in the structure? We are going to answer these questions in the next sections.
A Mark
composite object has primitive operations, so it may not be straightforward to work with structure directly. We need another class called Scribble
to manage it at a higher level. In the Observer pattern chapter (Chapter 12), we have discussed how a Scribble
object can interact as a model with view controllers like CanvasViewController
in the Model-View-Controller paradigm. A Scribble
object contains a Mark
instance structure that reflects the current state of what's drawn on the CanvasView
. The Scribble
object's role is not to just contain an instance of Mark
but also create a snapshot of it as a memento object and let it be kept safe by another object. The snapshot memento itself could be a complete structure of what the Scribble
object has at the moment or a portion of the structure as changes to the internal state. The saving-restore deal is only between the Scribble
object and its memento object, but no one else's. The Scribble
object doesn't know where its memento will be kept. So we need a caretaker object for keeping and passing around memento objects. A possible class for that role could be ScribbleManager
, which we briefly talked about in conjunction with the Façade pattern in Chapter 10. Its role is mainly responsible for managing all the chores related to saving and restoring Scribble
objects. Although we will not get into the details of the ScribbleManager
class, we'll briefly discuss how it can fit in the picture of the Memento pattern.
Let's recall some of our requirements about saving a scribble in Chapter 2. When the user has tapped a save button, then CanvasViewController
will capture what's on the CanvasView
as an image. Then it sends a message to an instance of ScribbleManager
to save the current Scribble
object with the just captured screenshot of the CanvasView
. Then the ScribbleManager
will take it from there. First of all, it sends a message to the Scribble
object to create a ScribbleMemento
object that contains a snapshot of its internal state. Once a ScribbleMemento
object is created and returned by the Scribble
object, the ScribbleManager
can have a choice either to save it in a file system or keep it in the memory for a while.
For now we are concerned only about saving the ScribbleMemento
object in the file system. The ScribbleManager
cannot save the returned ScribbleMemento
object just like that. A more convenient way for it to do so is to have the ScribbleMemento
appear in a form of NSData
, so the ScribbleManager
can save it in a file system with some file-related convenience methods available from the NSData
object.
We'll not get to all the hocus-pocus of saving thumbnails and whatnot in this chapter, but focus on saving a Scribble
object with a simpler method, saveScribble:
, from a ScribbleManager
object. The big picture of how these mentioned objects interact is illustrated in a sequence diagram in Figure 23-3.
CanvasViewController
acts as aClient
and sends a saveScribble:
message along with a Scribble
object in question to a ScribbleManager
object. Then inside the method, it will ask the Scribble
object to create its ScribbleMemento
object for saving. You may wonder why ScribbleManager
wouldn't just ask Scribble
to pass an NSData
object to it directly and save it from there. Using just NSData
directly is not enough to solve our problem, as the memento data itself can contain different information about the internal state of the Scribble
object. Sometimes we may want the Scribble
object just to save changes only. So we need a memento object to give us some clue how to restore a Scribble
, whether we need just to attach the saved state to the current one in the Scribble
or a complete restoration. The saved state in the memento object can be as complicated as it gets (as long as a Scribble
object can understand). Also the same ScribbleMemento
objects in memory can be reused in other areas of the application, such as (un)redoing of drawing a stroke or distributing them across the network to share with other users to simulate remote drawing. That said, if we used NSData
objects exclusively for a memory-bound state-saving strategy, the performance would suffer as the archiving-unarchiving process takes some extra effort.
We have just discussed how to save a Scribble
object. You might wonder how we can get it back from the file system.
In our earlier sections of this chapter, we have seen that a caretaker will eventually pass a previously saved memento back to an originator to restore one of its previous states. Sometimes it may not be the case. Our scribble saving/loading strategy is a typical example of using a memento without passing it back to its originator.
In Chapter 2, we saw that the user taps a thumbnail in question to open a saved scribble. An object that invokes the process could be a button or a separate command object (see the Command pattern, Chapter 20). The invoker that acts as a client sends a message to an instance of ScribbleManager
with all necessary information for it to load and return a Scribble
object from an archive (the file system). For an obvious reason, the invoker doesn't know where and how the scribble in question was saved.
Then the ScribbleManager
will load the corresponding NSData
object with a predefined location in the filesystem. The ScribbleManager
passes the data to a ScribbleMemento
's class method mementoWithData:data
to create an instance of ScribbleMemento
. At this point, instead of passing the ScribbleMemento
object back to a Scribble
originator, we get a brand new instance of Scribble
with the ScribbleMemento
object because we assume the life of the original Scribble
object should have ended before it was saved. Once the invoker gets the new instance of Scribble
with a restored state, it will pass it around in the application for further use. A particular use case with a resurrected Scribble
object is to ask CanvasViewController
to replace its current Scribble
object with the resurrected one (i.e., opening a saved scribble).
A sequence diagram of the interaction among all the mentioned classes and roles is shown in Figure 23-4.
Figure 23-4. A sequence diagram of the TouchPainter app on retrieving a stored Scribble
object as memento
You can see that the highlighted part of the diagram is similar to the same area in the sequence diagram for the original Memento pattern in Figure 23-1. The difference is we are not passing the loaded ScribbleMemento
object back to the very original Scribble
object that created it. You can, though, use a different operation to "attach" an internal state to an existing Scribble
object from a ScribbleMemento
object. We will discuss the details of that operation later in the implementation part of the chapter.
When we first introduced the concepts of the Memento pattern, we mentioned that a memento object should have wide interfaces for its originator but narrow interfaces for other objects, such as a caretaker. Wide interfaces (vs. narrow ones) offer more options and freedom to use the object. The Memento pattern suggests that wide interfaces should be available only to originators and mementos. Wide interfaces are usually declared as private
(operations or constructor) and friend
in other object-oriented languages like C++. With a friend
directive for local methods or classes, another class can access them like its own private resources. Everything in Objective-C is public, so we need some other trick to achieve the same thing.
There is a unique feature of Objective-C called categories. Categories allow developers to add extra methods to an existing class without subclassing. How can we use a category to make our wide interfaces private? All we need to do is create a category for ScribbleMemento
, called ScribbleMemento (Private)
. The ScribbleMemento (Private)
's interfaces will not be exposed to other classes unless they know where to get the declaration. So you can put private (or friend) operations in that category declaration so that they are available only to Scribble
. The public (narrow) interfaces of ScribbleMemento
are separated from what's declared in the ScribbleMemento (Private)
category. In Objective-C 2.0, you can use an anonymous category, e.g., ScribbleMemento()
, for declaring any private operations. It's called an extension. The main difference between an extension and a regular category is that an extension's operations should be defined in the class's principal implementation but a regular category's implementation can be in a separated file. Also, if the implementation of any operations declared in an extension can't be found, the compiler will give you some warnings. It's considered a better alternative to declare private methods instead of a regular category. But it doesn't mean the original category feature is obsolete. We still need it to extend some framework classes because we cannot modify their original class files.
A class diagram that illustrates idea around the basic design for ScribbleMemento
is shown in Figure 23-5.
ScribbleMemento
offers its narrow functionalities with the mementoWithData:data
and data
methods. The mementoWithData:data
method allows other classes to get an instance of ScribbleMemento
by providing an NSData
object. The data
method does the reverse: it returns an archived form of a ScribbleMemento
object as NSData
. Other classes should never be able to see the narrow interfaces defined in the private category.
Let's take a look at some code for what we have designed so far for ScribbleMemento
in Listing 23-1.
Example 23-1. A Class Declaration of ScribbleMemento
in ScribbleMemento.h
#import "Mark.h" @interface ScribbleMemento : NSObject { @private id <Mark> mark_; BOOL hasCompleteSnapshot_; } + (ScribbleMemento *) mementoWithData:(NSData *)data; - (NSData *) data; @end
ScribbleMemento
keeps a reference to a private Mark
object as the internal state for Scribble
. It also has a BOOL
private member variable, hasCompleteSnapshot_
, to tell a Scribble
object later whether the stored Mark
reference is a complete snapshot or just a fragment of it. The declarations for the narrow operations are in there too. We will get to the narrow operations in its implementation later.
Its extension (private category) declares wider interfaces that should be used by Scribble
objects only as shown in Listing 23-2.
Example 23-2. A Private Category of ScribbleMemento
Declared in ScribbleMemento+Friend.h
#import "Mark.h" #import"ScribbleMemento.h" @interface ScribbleMemento () - (id) initWithMark:(id <Mark>)aMark; @property (nonatomic, copy) id <Mark> mark; @property (nonatomic, assign) BOOL hasCompleteSnapshot; @end
A Scribble
object can create and initialize a ScribbleMemento
object with its own internal Mark
reference. The Scribble
object can access ScribbbleMemento
's mark
and hasCompleteSnapshot
properties as well.
Listing 23-3 shows its implementation.
Example 23-3. An Implementation of ScribbleMemento
in ScribbleMemento.m
#import "ScribbleMemento.h" #import "ScribbleMemento+Friend.h" @implementation ScribbleMemento
@synthesize mark=mark_; @synthesize hasCompleteSnapshot=hasCompleteSnapshot_; - (NSData *) data { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:mark_]; return data; } + (ScribbleMemento *) mementoWithData:(NSData *)data { // It raises an NSInvalidArchiveOperationException if data is not a valid archive id <Mark> retoredMark = (id <Mark>)[NSKeyedUnarchiver unarchiveObjectWithData:data]; ScribbleMemento *memento = [[[ScribbleMemento alloc] initWithMark:retoredMark] autorelease]; return memento; } - (void) dealloc { [mark_ release]; [super dealloc]; } #pragma mark - #pragma mark Private methods - (id) initWithMark:(id <Mark>)aMark { if (self = [super init]) { [self setMark:aMark]; } return self; } @end
The instance method, data
, sends a message, archivedDataWithRootObject:self
, to the NSKeyedArchiver
class to get an encoded version of self
and then returns it. The class method, mementoWithData:(NSData *)data
, creates a new instance of ScribbleMemento
with a provided NSData
object. data
is first being unarchived by the NSKeyedUnarchiver
object to decode it back to an instance of ScribbleMemento
before the instance is returned.
We @synthesize
'd the mark
and hasCompleteSnapshot
properties in the main implementation of Scribble
. We did so because @synthesize
'ing properties in a category is not allowed in Objective-C 2.0. Also the compiler enforces an extension, so its implementations should be put in the class's principal @implementation
section. As the properties are @synthesize
'd in the implementation, it won't break the privacy concerns for ScribbleMemento
objects.
Besides the private properties, we also put the initWithMark:
method definition in the main implementation here.
Something that is worth mentioning here is that the mark
property is actually copying another Mark
object, not just retaining it. Why is that? If it just retains it and the original Mark
object has been modified in other parts of the app, then the state we collected for the ScribbleMemento
object will be screwed. It seems the difference between copy and retain is small in code, but the impact could be immeasurable. It's similar to copying an instance of NSString
in a property instead of just retaining it as the string could be modified outside of the class.
We've implemented the ScribbleMemento
class. In order to let Scribble
and ScribbleMemento
work together, we need to make some modifications in the Scribble
class, as shown in Listing 23-4.
Example 23-4. A Modified Class Declaration of Scribble in Scribble.h
#import "Mark.h" #import "ScribbleMemento.h" @interface Scribble : NSObject { @private id <Mark> parentMark_;id <Mark> incrementalMark_;
} // methods for Mark management - (void) addMark:(id <Mark>)aMark shouldAddToPreviousMark:(BOOL)shouldAddToPreviousMark; - (void) removeMark:(id <Mark>)aMark;// methods for memento
- (id) initWithMemento:(ScribbleMemento *)aMemento;
+ (Scribble *) scribbleWithMemento:(ScribbleMemento *)aMemento;
- (ScribbleMemento *) scribbleMemento;
- (ScribbleMemento *) scribbleMementoWithCompleteSnapshot:(BOOL)hasCompleteSnapshot;
- (void) attachStateFromMemento:(ScribbleMemento *)memento;
@end
We've added another Mark
reference to Scribble
as incrementalMark_
, which is used for keeping a reference to a complete stroke or dot added to parentMark_
.
There are several methods for use with ScribbleMemento
objects exclusively. We've seen some of those in the preceding sections when we were designing the architecture for implementing the pattern. We'll go through the details of each of them in a modified version of the implementation of Scribble
in Listing 23-5. The code is quite long, so we will break it up into a few chunks and walk through it.
Example 23-5. A Modified Implementation of Scribble
in Scribble.m
#import "ScribbleMemento+Friend.h"
#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;
}
We import the private category that we have defined in Listing 23-2 with which we can create a ScribbleMemento
object inside Scribble
. A Scribble
object contains a reference to a Mark
object, which is considered the main parent of a Mark
composite structure. The Mark
property is an internal state of a Scribble
object, and it is exposed only to itself.
Please refer to Chapter 12 for a detailed discussion about operations related to Mark
objects in the Scribble
class.
#pragma mark - #pragma mark Methods for Mark management // please refer to Chapter 12 for the detail of // the methods related to 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];incrementalMark_ = 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];// we don't need to keep the
// incrementalMark_ reference
// as it's just removed in the parent
if (aMark == incrementalMark_)
{
incrementalMark_ = nil;
}
// manual KVO invocation [self didChangeValueForKey:@"mark"]; }
We have added a statement in the addMark:shouldAddToPreviousMark:
method to save aMark
as incrementalMark_
when it is being attached directly to the main parent mark (i.e., a stroke or a dot).
If we're deleting a Mark
object that is also being referenced in incrementalMark_
, then we need to set it to nil afterward. Otherwise, it may crash the whole application if it will be used again.
#pragma mark -
#pragma mark Methods for memento
- (id) initWithMemento:(ScribbleMemento*)aMemento
{
if (self = [super init])
{
if ([aMemento hasCompleteSnapshot])
{
[self setMark:[aMemento mark]];
}
else
{
// if the memento contains only
// incremental mark, then we need to
// create a parent Stroke object to
// hold it
parentMark_ = [[Stroke alloc] init];
[self attachStateFromMemento:aMemento];
}
}
return self;
}
A Scribble
object can be initialized with a ScribbleMemento
object with which the Scribble
object can restore its own state with access to the private mark
property of the ScribbleMemento
object.
- (void) attachStateFromMemento:(ScribbleMemento *)memento
{
// attach any mark from a memento object
// to the main parent
[self addMark:[memento mark] shouldAddToPreviousMark:NO];
}
The attachStateFromMemento:
method allows a Scribble
object to add any Mark
object from a ScribbleMemento
object. Since our design deals only with incremental Mark
objects that were attached to the main parent before, we add the memento's Mark
object to the main parent here as well.
- (ScribbleMemento *) scribbleMementoWithCompleteSnapshot:(BOOL)hasCompleteSnapshot
{
id <Mark> mementoMark = incrementalMark_;
// if the resulting memento asks
// for a complete snapshot, then
// set it with parentMark_
if (hasCompleteSnapshot)
{
mementoMark = parentMark_;
}
// but if incrementalMark_
// is nil then we can't do anything
// but bail out
else if (mementoMark == nil)
{
return nil;
}
ScribbleMemento *memento = [[[ScribbleMemento alloc]
initWithMark:mementoMark] autorelease];
[memento setHasCompleteSnapshot:hasCompleteSnapshot];
return memento;
}
- (ScribbleMemento *) scribbleMemento
{
return [self scribbleMementoWithCompleteSnapshot:YES];
}
+ (Scribble *) scribbleWithMemento:(ScribbleMemento *)aMemento
{
Scribble *scribble = [[[Scribble alloc] initWithMemento:aMemento] autorelease];
return scribble;
}
- (void) dealloc { [parentMark_ release]; [super dealloc]; } @end
In the scribbleMementoWithCompleteSnapshot:
method, if the hasCompleteSnapshot
parameter is YES
, then it will create an instance of ScribbleMemento
with parentMark_
, otherwise incrementalMark_
.
scribbleMemento
is a convenience method that uses the scribbleMementoWithCompleteSnapshot:
method with a BOOL
parameter set to YES
to create and return a ScribbleMemento
object that contains a complete snapshot of the current state.
Another method, scribbleWithMemento:
, is a class method that creates a new Scribble
object with a ScribbleMemento
object.
We've got everything set up except that we're still missing the part where a caretaker manages ScribbleMemento
objects. In the earlier part of the chapter, we assumed that we could use ScribbleManager
to play the caretaker role for us. It offers a simple operation called saveScribble:
to let clients save any Scribble
object they have. It also provides a scribbleAtIndex:
method that takes an index that identifies a particular Scribble
object with which a ScribbleManager
object will load and return.
Let's take a look at some implementation code that is simplified for the saveScribble:
method and see how it captures the internal state of a Scribble
object and saves it in the file system in Listing 23-6.
Example 23-6. Simplified Code for saveScribble:
That Saves a ScribbleMemento
Object in the File System
// get a memento from the scribble ScribbleMemento *scribbleMemento = [scribble scribbleMemento]; // get an NSData object from the memento // so we can use it to save itself in the // file system NSData *mementoData = [scribbleMemento data]; NSString *mementoPath; // ... // construct the path for saving // the memento and perform any other // operations before actually saving it // in the file system // ... [mementoData writeToFile:mementoPath atomically:YES];
We first ask a Scribble
object to get us a ScribbleMemento
instance with its scribbleMemento
method. But we can't save the ScribbleMemento
object in the file system as-is. So we need it to package itself as an NSData
object with a data message call to the ScribbleMemento
object. Then we save the NSData
object with a path in the file system.
The resurrection operation is pretty much like the saving one, as shown in Listing 23-7. Likewise, it is also a simplified version of the scribbleAtIndex:
method.
Example 23-7. Simplified Code for scribbleAtIndex:
That Retrieves a ScribbleMemento
Object from the File System, and Then Restores It As a Scribble
Object
NSString *scribbleMementoPath; // ... // use the provided index to retrieve // the path that was used for saving // the memento before. We will use the // path to load the memento later // ... // use NSFileManager to load the memento file // as NSData with the path that we just reconstructed NSFileManager *fileManager = [NSFileManager defaultManager]; NSData *scribbleMementoData = [fileManager contentsAtPath:scribbleMementoPath]; // we create a ScribbleMemento from // the NSData object. Then we use the // memento to resurrect a Scribble object // based on what's saved in the memento ScribbleMemento *scribbleMemento = [ScribbleMemento mementoWithData:scribbleMementoData]; Scribble *resurrectedScribble = [Scribble scribbleWithMemento:scribbleMemento];
First we use the provided index to locate the path that points to the saved data file in the file system. Then we load the NSData
object back from its archive file and use the data object to create a ScribbleMemento
object with a class message mementoWithData:
. Finally, we use the ScribbleMemento
object to resurrect a Scribble
object with scribbleWithMemento:
.
The Cocoa Touch framework has adopted the Memento pattern with archiving, property list serialization, and core data. Property list serialization and core data are outside the scope of this book. We will focus only on archiving and briefly go through its key features and how it can be applied to our example in the previous sections.
Cocoa archiving encodes objects, along with their properties and relationships with other objects, in an archive that can be stored in the file system or transmitted between processes or across a network. The relationships of the objects with other objects are treated as a network of object graphs. The archiving process captures the object graph as an architecture-independent byte stream that preserves the identity of the objects and relationships among them. The objects' types are also stored along with their data. Objects decoded from the byte stream are normally instantiated with the same classes of objects that were originally encoded.
When we want to archive an object, most of the time we think about saving the program's state. In the Model-View-Controller paradigm, the program's states are usually maintained in model objects. You encode a model object in an archive, and you read it back by decoding it. The encoding and decoding operations are performed using an NSCoder
object at runtime. NSCoder
itself is just an abstract class. Apple suggests using the keyed archiving technique with NSKeyedArchiver
and NSKeyedUnarchiver
classes, which are concrete classes of NSCoder
. The object being encoded and decoded must conform to the NSCoding
protocol and implement the following methods:
- (id)initWithCoder:(NSCoder *)coder; - (void)encodeWithCoder:(NSCoder *)coder;
These methods are related to encoding and decoding the object during the archiving and unarchiving processes. We'll discuss the methods in a little bit.
In the example of the ScribbleMemento
, we have implemented the archiving and unarchiving processes with the NSKeyedArchiver
and NSKeyedUnarchiver
classes. The object being encoded was a Mark
composite object. You can go to Chapter 13 for the discussion on Mark
and its composite related operations. In order to make NSKeyedArchiver
and NSKeyedArchiver
happy to do the job for us, we need to make sure all Mark
classes conform to the NSCoding
protocol and its required methods. So first of all, we need the Mark
protocol to adopt NSCoding
so other classes will follow, as shown in Listing 23-8.
Example 23-8. Mark
Implements the NSCoding
Protocol
@protocol Mark <NSObject, NSCopying, NSCoding> @property (nonatomic, retain) UIColor *color; @property (nonatomic, assign) CGFloat size; @property (nonatomic, assign) CGPoint location; @property (nonatomic, readonly) NSUInteger count; @property (nonatomic, readonly) id <Mark> lastChild; - (id) copy; - (void) addMark:(id <Mark>) mark; - (void) removeMark:(id <Mark>) mark; - (id <Mark>) childMarkAtIndex:(NSUInteger) index; // some other methods defined in other chapters @end
So all Mark
's implementers should be NSCoding
-compliant until they all implement the required methods. Let's look at Vertex
's implementation of these methods in Listing 23-9.
Example 23-9. NSCoding
's Method Implementation in Vertex
- (id)initWithCoder:(NSCoder *)coder { if (self = [super init]) { location_ = [(NSValue *)[coder decodeObjectForKey:@"VertexLocation"] CGPointValue]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:[NSValue valueWithCGPoint:location_] forKey:@"VertexLocation"]; }
coder
is provided at runtime by an archiver or unarchiver with which we need to instruct it how to encode/decode an object with the same key. In the case of Vertex
, when the encodeWithCoder:
method is invoked at runtime when it is saving its object, it sends an encodeObject:
message to encode its location_
attribute as a key, @"VertexLocation"
. And it uses the same key to decode the same attribute in the initWithCoder:
method with a message, decodeObjectForKey:
, to an unarchiver.
Dot
follows pretty much the same procedure, except its objects have a couple more attributes to take care of, as shown in Listing 23-10.
Example 23-10. NSCoding
's Method Implementation in Dot
- (id)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { color_ = [[coder decodeObjectForKey:@"DotColor"] retain]; size_ = [coder decodeFloatForKey:@"DotSize"]; }
return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeObject:color_ forKey:@"DotColor"]; [coder encodeFloat:size_ forKey:@"DotSize"]; }
The reason we don't forward the same message to super
in the initWithCoder:
and encodeWithCoder:
methods of Vertex
is that Vertex
is a direct subclass of NSObject
and NSObject
does not implement those methods.
The implementation for Stroke
looks very similar to Vertex
's and Dot
's, as in Listing 23-11.
Example 23-11. NSCoding
's Method Implementation in Stroke
- (id)initWithCoder:(NSCoder *)coder { if (self = [super init]) { color_ = [[coder decodeObjectForKey:@"StrokeColor"] retain]; size_ = [coder decodeFloatForKey:@"StrokeSize"]; children_ = [[coder decodeObjectForKey:@"StrokeChildren"] retain]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:color_ forKey:@"StrokeColor"]; [coder encodeFloat:size_ forKey:@"StrokeSize"]; [coder encodeObject:children_ forKey:@"StrokeChildren"]; }
The children of a Stroke
object can be encoded recursively as well. We just toss the whole children attribute in coder
during encoding, and we can get it back in one piece from the coder
of an unarchiver in the initWithCoder:
method.
So far, we have discussed the concepts of the Memento pattern and how we can apply it to an iOS application development with the TouchPainter app example. ScribbleMemento
can also be reused for implementing (un)redo operations by keeping a list of small changes to the internal state of a Scribble
object. A list of ScribbleMemento
objects that carry only small changes of the Scribble
can be useful for sharing strokes across the network. So the other remote user who is sharing the same drawing session can see a simulated remote drawing, one stroke at a time.
This marks the end of the catalog for all the design patterns presented in this book. Now, you should feel quite comfortable using any of them for your real projects. I do hope that you can find them useful in many aspects of your software design ventures!
[21] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
3.144.119.170