Chapter 23. Memento

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.

What's 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.

Note

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.

A class diagram of the structure of the Memento pattern

Figure 23-1. A class diagram of the structure of the Memento pattern

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.

A sequence diagram of the Memento pattern

Figure 23-2. A sequence diagram of the Memento pattern

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 Originators 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.

When Would You Use the Memento Pattern?

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.

Using the Memento Pattern in TouchPainter

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.

Saving a Scribble

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.

A sequence diagram of the TouchPainter app on storing a Scribble object as a memento

Figure 23-3. A sequence diagram of the TouchPainter app on storing a Scribble object as a memento

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.

Restoring a Scribble

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.

A sequence diagram of the TouchPainter app on retrieving a stored Scribble object as memento

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.

Designing and Implementing ScribbleMemento

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.

A class diagram of the ScribbleMemento and its related classes

Figure 23-5. A class diagram of the ScribbleMemento and its related classes

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.

Implementing the ScribbleMemento Class

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.

Modifying the Scribble 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.

Putting Everything Together with a Caretaker

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 Memento Pattern in the Cocoa Touch Framework

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.

Summary

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).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.119.170