Chapter 20. Command

In a battlefield, generals hand out timed instructions in sealed envelopes to soldiers or others, who then open the envelopes and execute the instructions in them. The same instructions can be one-timed or re-executed by different persons at given times. Since instructions are kept in envelopes, passing them around different areas for different purposes is easier than other means of instructions—for example, verbal instruction on the phone or other communication channels.

We borrow a similar idea of encapsulating instructions as different command objects in object-oriented design. Command objects can be passed around and reused at given times by different clients. A design pattern that is elaborated from the concept is called the Command pattern.

In this chapter, we are going to discuss the concepts of the pattern. We will also develop an undo infrastructure for our TouchPainter app so the user can redo/undo what he/she has drawn on the screen. The Cocoa Touch adaptation of the pattern will also be discussed later in the chapter.

What Is the Command Pattern?

A command object encapsulates information regarding how to perform instructions on a target, so a client or an invoker doesn't need to know any details of the target but can still be able to perform any available operations on it. By encapsulating a request as an object, clients can parameterize and put it in a queue or log, as well as support undoable operations. The command object binds together one or more actions with a specific receiver. The Command pattern decouples the binding between an action as an object and a receiver that executes it.

Before we dive into the details of the pattern in Objective-C, let's take a brief look at the structure of the pattern as illustrated in Figure 20-1.

A class diagram that shows the structure of the Command pattern

Figure 20-1. A class diagram that shows the structure of the Command pattern

So, here is what's happening in the diagram:

  • The client creates a ConcreteCommand object and sets its receiver.

  • The Invoker asks a generic command (in fact, ConcreteCommand) to carry out the request.

  • Command is a generic interface (protocol) that is known to Invoker.

  • ConcreteCommand acts as a middleman between Receiver and the action operation for it.

  • Receiver can be any object that performs an actual operation with a corresponding request being carried out by a Command (ConcreteCommand) object.

Note

COMMAND PATTERN: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.[18]

When Would You Use the Command Pattern?

You'd naturally think about using the pattern when

  • You want your application to support undo/redo.

  • You want to parameterize an action as an object to perform operations and replace callbacks with different command objects

  • You want to specify, queue, and execute requests at different times.

  • You want to log changes so they can be reapplied later in case of a system failure.

  • You want the system to support transactions with which a transaction encapsulates a set of changes to data. The transactions can be modeled as command objects.

You may ask, "Why can't we just execute a method directly without all the hassles?" Technically, yes, you can call whatever methods or functions you want in your program, and your program still compiles and runs. The problem is every class or method needs to know each other's details in order to work, and it's very complicated to implement any other operations, such as undo. When the program grows larger and larger, it would go to the point where it is very difficult to manage and reuse objects.

Before we get to designing and implementing an undo infrastructure, let's look at some useful resources from the Cocoa Touch framework that we can use for setting up the infrastructure in the next section.

Using the Command Pattern in the Cocoa Touch Framework

Instead of reinventing the wheel, it is a virtue to reuse what is available in the Cocoa Touch framework. The Command pattern is one of cataloged patterns adapted by the framework. By using "off-the-shelf" classes in the framework, we can focus on content development.

NSInvocation, NSUndoManager, and the target-action mechanism are typical adaptations of the pattern in the framework. The target-action mechanism has been covered in many other beginner iOS programming books, so we will go over NSInvocation and NSUndoManager only.

NSInvocation Objects

An instance of the NSInvocation class is just like the original ConcreteCommand class shown in Figure 20-1. An NSInvocation object encapsulates any necessary information to forward an execution message to a receiver at runtime, such as a target object, method selector, and method arguments. So the receiver can be invoked with the embedded selector and other information anytime with the help of the NSInvocation instance. The same invocation instance can repeatedly invoke the same method on the receiver or be reused with different targets and methods signatures.

NSInvocation is a generic layer of abstraction for invocation. Framework designers at Apple apparently didn't know anything about our actual receivers, invokers, selectors, and such when they were designing the infrastructure for the NSInvocation class. So at the creation of an NSInvocation object, we need to provide any necessary information as a form of an NSMethodSignature object. An NSMethodSignature object contains any argument and returns value types of a method invocation. To elaborate from our rather generic example at the beginning of the chapter, we are now going to use NSInvocation in place of the original Command and ConcreteCommand classes as illustrated in Figure 20-2.

A new class diagram of the Command pattern with the NSInvocation class

Figure 20-2. A new class diagram of the Command pattern with the NSInvocation class

We took off the original Command protocol from our first class diagram, and we replaced the ConcreteCommand class with the NSInvocation class provided by the Cocoa Touch framework. The idea is that a client creates a new instance of NSInvocation object with an instance of Receiver and its operation as a selector. Then an Invoker (e.g., a UIButton object) will be set with the NSInvocation object. When the Invoker needs to invoke the action, it will call the invoke method of the NSInvocation object that is stored in the Invoker. Finally, the NSInvocation object will invoke the selector on its target to finish the process.

NSUndoManager

Since the introduction of iOS 3.0, NSUndoManager has been available in the Cocoa Touch framework for iOS application development. NSUndoManager was elegantly designed as a general-purpose undo stack management class, yet it is powerful and versatile enough to use in almost any type of application. We will go over some key features and the idea of NSUndoManager here.

An instance of NSUndoManager manages its own undo and redo stacks. The undo stack maintains all the invoked operations as objects. NSUndoManager can also "reverse" the undo operation (a.k.a. redo) by invoking the operation object that was pushed from the undo stack. A registered undo operation can be an action that reverses a previous action invoked by a client. Instead of home-brewing our own undo stack, NSUndoManager saves us all the hassle of dealing with undo as well as redo operations by using various delayed invocation forwarding mechanisms. An undo manager object collects all registered undo operations that occur within a single-run loop cycle, so that an undo can reverse all changes within the cycle. When an NSUndoManager object is requested to undo the last operation, it will invoke the operation object at the top of the undo stack. When it is done, it will pop the operation object and push it to the redo stack. Likewise when redo is finished, it will move the operation object from the redo stack to the undo stack for the next undo. So NSUndoManager exchanges operation objects between two stacks to manage the whole command history.

Registering Undo Command Operations

Initially, the undo stack is empty. Registering an undo operation adds an invocation object to the undo stack. To add an undo operation to the undo stack, you need to register it with the object that performs the undo operation. There are two ways to register undo operations with an NSUndoManager object:

  • Simple undo registration

  • Invocation-based registration

Registering a simple undo operation requires a selector that identifies an undo operation with a receiver as arguments. The state is passed to the undo manager prior to any changes. When the undo operation is invoked, the receiver will be reset with the previous state or attributes.

The invocation-based undo involves an NSInvocation object. Using an NSInvocation object means it can take a method with any number and type of parameters. This approach is very useful when a state change requires multiple parameters.

In most cases, a client object that contains or manages other objects in an application owns an instance of NSUndoManager. Since NSUndoManager doesn't retain its invocation target receivers, the client object needs to make sure the receivers being pushed in the NSUndoManager's undo stack have a reference count of at least 1. Otherwise, the undo manager may crash the application if one of its receivers is released when performing an undo/redo. Sometimes, there are cases where an object being modified can have its own undo manager and perform its own undo and redo operations—for example, a drawing app that has multiple drawing views, each of which keeps track of all individual strokes and their colors. Each new stroke added to an active drawing view will register a new undo operation with the view's undo manager. If an undo operation is requested on a particular view, the corresponding undo manager will perform the registered undo operation on the receiver so that the last stroke will be removed.

Implementing Undo/Redo in TouchPainter

As we described in the previous sections, one of the highlighted uses of the Command pattern is to support undo/redo operations in an application.

We are about to design and implement an undo/redo architecture for the TouchPainter app. In the preceding sections, we have discussed using NSInvocation to encapsulate an execution as a command object. We have also explored the possibility of borrowing the NSUndoManager's undo architecture for our own use.

In this section, we will have two sections for different approaches to implement that. In the first one, we will use NSUndoManager for the design. After that, we will discuss how to build our own version of undo/redo from scratch.

Implementing Drawing/Undrawing with NSUndoManager

We've already learned some key concepts and features of NSUndoManager in the preceding sections. In the subsections that follow, we will walk through the process of using NSUndoManager to manage actionable NSInvocation objects for drawing and undrawing strokes and dots against the main Scribble object.

Adding Methods for Generating Drawing/Undrawing Invocations

We need a new NSInvocation object each time when we need the NSUndoManager to register an undo/redo operation. It is handy to have a method or two that can generate prototype NSInvocation objects with which we can just modify a couple of parameters of them with some particular Stroke or Dot objects on the go. Listing 20-1 shows two methods just for that purpose.

Example 20-1. Methods That Generate NSInvocation Objects for Performing Drawing and Undrawing Operations Later

- (NSInvocation *) drawScribbleInvocation
{
  NSMethodSignature *executeMethodSignature = [scribble_
                                               methodSignatureForSelector:
                                               @selector(addMark:
                                                         shouldAddToPreviousMark:)];
  NSInvocation *drawInvocation = [NSInvocation
                                  invocationWithMethodSignature:
                                  executeMethodSignature];
  [drawInvocation setTarget:scribble_];
  [drawInvocation setSelector:@selector(addMark:shouldAddToPreviousMark:)];
  BOOL attachToPreviousMark = NO;
  [drawInvocation setArgument:&attachToPreviousMark atIndex:3];

  return drawInvocation;
}

- (NSInvocation *) undrawScribbleInvocation
{
  NSMethodSignature *unexecuteMethodSignature = [scribble_
                                                 methodSignatureForSelector:
                                                 @selector(removeMark:)];
  NSInvocation *undrawInvocation = [NSInvocation
                                    invocationWithMethodSignature:
                                    unexecuteMethodSignature];
  [undrawInvocation setTarget:scribble_];
  [undrawInvocation setSelector:@selector(removeMark:)];
return undrawInvocation;
}

The drawScribbleInvocation method generates an NSInvocation object that will be used for adding a Mark object to scribble_. It requires an NSMethodSignature object for a selector that will be invoked to instantiate an NSInvocation object. In this case, we need a method signature for the addMark:shouldAddToPreviousMark: method of a Scribble object. After we create an NSInvocation object, we set the second argument (at index 3 from 0) of a BOOL value default to NO because we undo/redo only complete strokes and dots, not vertices. User arguments collected in the NSInvocation object begin at index 2 because the first one (at index 0) is the receiver and the second one is _cmd that contains the name of the selector being invoked.

Likewise, the undrawScribbleInvocation method creates an NSInvocation object the same way except that its selector for the invocation is now removeMark: from a Scribble object. The invocation object is for undoing a complete stroke or dot later. When we get to the actual drawing code, we'll see how we can assign a real Mark object to each of invocation objects generated from the methods.

Adding Methods for Registering Undo/Redo Operations in NSUndoManager

We've seen how to generate NSInvocation objects for drawing/undrawing strokes and dots in action, but we also need to register them with the NSUndoManager to make those invocations undoable. Listing 20-2 shows the implementation of the methods that are used for registering undo/redo operations with the NSUndoManager of CanvasViewController.

Example 20-2. Methods for Executing and Unexecuting NSInvocation Objects with Help from NSUndoManager

#pragma mark Draw Scribble Command Methods

- (void) executeInvocation:(NSInvocation *)invocation
        withUndoInvocation:(NSInvocation *)undoInvocation
{
  [invocation retainArguments];

  [[self.undoManager prepareWithInvocationTarget:self]
   unexecuteInvocation:undoInvocation
   withRedoInvocation:invocation];

  [invocation invoke];
}

- (void) unexecuteInvocation:(NSInvocation *)invocation
          withRedoInvocation:(NSInvocation *)redoInvocation
{
  [[self.undoManager prepareWithInvocationTarget:self]
   executeInvocation:redoInvocation
   withUndoInvocation:invocation];

  [invocation invoke];
}

Both the executeInvocation:withUndoInvocation: and unexecuteInvocation: withRedoInvocation: methods look very similar and somewhat confusing. The first method takes an invocation object to execute straightaway and registers another invocation object as an undo operation. The latter method uses an invocation object in the first parameter to execute undo operations and registers the second one for redo.

In the executeInvocation: method, we send a prepareWithInvocationTarget: method to CanvasViewController's NSUndoManager to register an undo operation. We pass in unexecuteInvocation:undoInvocation withRedoInvocation:invocation as a complete invocation message for an event of undo. The unexecuteInvocation: method registers the invocation object in executeInvocation: as a redo operation. If you look closely, you will notice that they are crisscrossing with an invocation object that draws and another invocation object that undraws what has been drawn.

Modifying Touch Event Handlers for Invocations

Now we are getting to change the original touch event handlers in CanvasViewController to create invocation objects to prepare all the drawing actions for undo/redo. Listing 20-3 shows the required modifications. We'll break it up into two parts to discuss.

Example 20-3. Modifications to the Original Touch Event Handlers in CanvasViewController That Accommodate the NSInvocation-Based Undo/Redo Operations with NSUndoManager

#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];

    // retrieve a new NSInvocation for drawing and
    // set new arguments for the draw command
    NSInvocation *drawInvocation = [self drawScribbleInvocation];
    [drawInvocation setArgument:&newStroke atIndex:2];

    // retrieve a new NSInvocation for undrawing and
// set a new argument for the undraw command
    NSInvocation *undrawInvocation = [self undrawScribbleInvocation];
    [undrawInvocation setArgument:&newStroke atIndex:2];

    // execute the draw command with the undraw command
    [self executeInvocation:drawInvocation withUndoInvocation:undrawInvocation];
  }

  // add the current touch as another vertex to the
  // temp stroke
  CGPoint thisPoint = [[touches anyObject] locationInView:canvasView_];
  Vertex *vertex = [[[Vertex alloc]
                     initWithLocation:thisPoint]
                    autorelease];

  // we don't need to undo every vertex
  // so we are keeping this
  [scribble_ addMark:vertex shouldAddToPreviousMark:YES];
}

The touch event handlers in this listing are discussed in the "Updating Strokes on the CanvasView in TouchPainter" section of Chapter 12. So we will not repeat ourselves with its details here but will highlight some key areas of the touch handling processes and the changes we make for undo/redo.

In the touchesMoved: method, the type of Mark object is determined by two situations:

  • If the previous touch was the first touch on the screen, then we will create an instance of Stroke and attach it to scribble_ under the main parent.

  • Other touches in the method will be treated as vertices of a stroke. So we will create an instance of Vertex and attach it to scribble_ under that last child Mark (i.e., a Stroke we added before) in the main parent.

At the point where it identifies the beginning of a Stroke object, we removed the original message statement of addMark:shouldAddToPreviousMark: for scribble_ for adding a new Stroke object. Instead, we use the *scribbleInvocation methods (* here is a general purpose wildcard character, not a notation for a pointer) defined previously to generate template invocation objects for adding and removing the new Stroke object.

After setting appropriate arguments for the drawInvocation and undrawInvocation objects, we set them for executeInvocation: to start executing the drawing operation. drawInvocation will be invoked in the method, and at the same time undrawInvocation will be registered as an undo operation.

- (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 lifted 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];

    // retrieve a new NSInvocation for drawing and
    // set new arguments for the draw command
    NSInvocation *drawInvocation = [self drawScribbleInvocation];
    [drawInvocation setArgument:&singleDot atIndex:2];

    // retrieve a new NSInvocation for undrawing and
    // set a new argument for the undraw command
    NSInvocation *undrawInvocation = [self undrawScribbleInvocation];
    [undrawInvocation setArgument:&singleDot atIndex:2];

    // execute the draw command with the undraw command
    [self executeInvocation:drawInvocation withUndoInvocation:undrawInvocation];
  }

  // 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
{
  // reset the start point here
  startPoint_ = CGPointZero;
}

When it comes to drawing a dot in the touchesEnded: method, we follow the same procedure we did for drawing a stroke. We use the same singleDot object for the executeInvocation: method.

So far we have implemented the undo/redo infrastructure with the help of NSUndoManager. In the following sections, we are going to discuss how to build our own undo/redo from the ground up. Free feel to read on if you are interested in knowing how a bare-bones undo/redo can be implemented with the Command pattern in Objective-C. Otherwise, you can skip to the "Allowing the User to Activate Undo/Redo" section toward the end of the chapter.

Home-Brewing Your Own Drawing/Undrawing Infrastructure

Congratulations! You have made it to the point where we are about to brewing our own undo/redo architecture. Before we get to the design, let's look at how we can collect and put together all the commands executed in an application. Executed command objects can be collected and queued as a list of command history, as shown in Figure 20-3.

A sequence of executed commands forms a linear command history

Figure 20-3. A sequence of executed commands forms a linear command history

As long as there is a new command object executed and added to the list, the list can keep growing in one direction. In a traditional sense, you can perform undo or redo operations with any one of them by traversing the command history list. There are many approaches to implementing a command history. One of the common ways is to use an index to keep track of the current command objects in the list. By increasing or decreasing the index value, you can navigate to a particular command object for either undo or redo. In fact, using indexing to navigate for either undo or redo operations can be quite tricky. There is a simpler and less error-prone way. Instead of using a single command list for both undo and redo, we can have one stack for undo and another one for redo just like NSUndoManager. Executed command objects are pushed to the undo stack. The one at the top is always the last command executed. When an application needs to undo the last execution, it will pop the last command off the undo stack and then undo it. When it's done, the application will push the command to the redo stack. Until all the command objects have been undone, the redo stack is already filled up with all the undone command objects. A similar procedure applies to redo, just in a different direction. A diagram that illustrates this mechanism is shown in Figure 20-4.

Transferring aCommand from the top of the undo stack to a redo stack during an undo operation

Figure 20-4. Transferring aCommand from the top of the undo stack to a redo stack during an undo operation

Command objects are just being popped and pushed between the two stacks without using any complex indexing scheme to navigate the list.

Designing a Command Infrastructure

We are going to implement undo/redo operations with the two-stack approach for our faithful TouchPainter app. When a user touches the canvas, either a stroke or a dot shows up on the screen. First of all, we need to make a "drawing" action as a command object. After it draws something, the app will push it to the undo stack until we need it back to undraw what it did. We call that command for drawing DrawScribbleCommand, as shown in a class diagram in Figure 20-5.

A class diagram of DrawScribbleCommand and related classes

Figure 20-5. A class diagram of DrawScribbleCommand and related classes

Command is the parent abstract class for DrawScribbleCommand. It declares some common abstract operations of execute and undo. Command also has a concrete userInfo property that allows clients to provide any extra parameters for a Command object. DrawScribbleCommand has a reference to a Scribble object so it can add or remove any Mark objects from it. CanvasViewController here is both a client and invoker. The overall class structure is quite similar to the original class diagram for the Command pattern in Figure 20-1.

Implementing the Command Classes

So, how can we implement it in Objective-C? Let's take a look at the abstract Command class in Listing 20-4.

Example 20-4. A Class Declaration of Command in Command.h

@interface Command : NSObject
{
  @protected
  NSDictionary *userInfo_;
}

@property (nonatomic, retain) NSDictionary *userInfo;

- (void) execute;
- (void) undo;

@end

Besides the execute and undo methods, Command declares the userInfo property as NSDictionary. Objects of Command's subclasses can use what's in the userInfo to perform any operations in an overridden execute method. The implementation of Command is shown in Listing 20-5.

Example 20-5. The Implementation of Command in Command.m

#import "Command.h"


@implementation Command
@synthesize userInfo=userInfo_;

- (void) execute
{
  // should throw an exception.
}

- (void) undo
{
  // do nothing
  // subclasses need to override this
  // method to perform actual undo.
}

- (void) dealloc
{
[userInfo_ release];
  [super dealloc];
}

@end

There is not much going on in the implementation of Command as both the execute and undo methods are abstract operations. Its subclasses should put some actions in them. Now let's take a look at DrawScribbleCommand in Listing 20-6.

Example 20-6. A Class Declaration of DrawScribbleCommand in DrawScribbleCommand.h

#import "Command.h"
#import "Scribble.h"

@interface DrawScribbleCommand : Command
{
  @private
  Scribble *scribble_;
  id <Mark> mark_;
  BOOL shouldAddToPreviousMark_;
}

- (void) execute;
- (void) undo;

@end

It declares some private member variables to help perform some operations on a Scribble object later. We are also re-declaring the execute and undo methods in the subclass, so when we look at this class again in the future we won't get lost. The implementation of execute and undo methods is shown in Listing 20-7.

Example 20-7. The Implementation of DrawScribbleCommand in DrawScribbleCommand.m

#import "DrawScribbleCommand.h"


@implementation DrawScribbleCommand

- (void) execute
{
  if (!userInfo_) return;

  scribble_ = [userInfo_ objectForKey:ScribbleObjectUserInfoKey];
  mark_ = [userInfo_ objectForKey:MarkObjectUserInfoKey];
  shouldAddToPreviousMark_ = [(NSNumber *)[userInfo_
                                           objectForKey:AddToPreviousMarkUserInfoKey]
                               boolValue];

  [scribble_ addMark:mark_ shouldAddToPreviousMark:shouldAddToPreviousMark_];

}

- (void) undo
{
  [scribble_ removeMark:mark_];
}
@end

The overridden execute method is dependent on the information embedded in the userInfo property. If there is no userInfo provided, the method will just bail out. OK, so what's in the userInfo that is so important to DrawScribbleCommand? The userInfo dictionary package contains three key elements that are crucial for using a Scribble object—first of all, a target Scribble object. Without that, nothing else matters. Then there is an instance of Mark that should be added to the Scribble object. Finally, a BOOL value tells how the Mark instance should be attached in the Scribble object. For a detailed discussion of Scribble and its operations, see the Observer pattern, Chapter 12.

The undo method simply tells the stored Scribble object to remove a stored Mark reference.

Modifying CanvasViewController for the Commands

Now we know how a DrawScribbleCommand performs actual drawing with a Scribble object. Let's see how a bunch of DrawScribbleCommand objects are involved in undo/redo operations defined in CanvasViewController, as shown in Listing 20-8.

Example 20-8. Methods That Manage Command Objects That Draw Scribbles in CanvasViewController

#pragma mark -
#pragma mark Draw Scribble Command Methods

- (void) executeCommand:(Command *)command
         prepareForUndo:(BOOL)prepareForUndo
{
  if (prepareForUndo)
  {
    // lazy-load undoStack_
    if (undoStack_ == nil)
    {
      undoStack_ = [[NSMutableArray alloc] initWithCapacity:levelsOfUndo_];
    }

    // drop the bottom one if the
    // undo stack is full
    if ([undoStack_ count] == levelsOfUndo_)
    {
      [undoStack_ dropBottom];
    }

    // push the command
    // to our undo stack
    [undoStack_ push:command];
  }

  [command execute];
}

The executeCommand:prepareForUndo: method is responsible for executing an incoming Command object as well as pushing it in undoStack_. The method also maintains the size of undoStack_ by dropping the one at the bottom if the stack is already full.

- (void) undoCommand
{
  Command *command = [undoStack_ pop];
  [command undo];

  // push the command to the redo stack
  if (redoStack_ == nil)
  {
    redoStack_ = [[NSMutableArray alloc] initWithCapacity:levelsOfUndo_];
  }

  [redoStack_ push:command];
}

When the undoCommand method is invoked, it first tries to retrieve a reference to the last command from the top of undoStack_, and then sends it to an undo message to undraw the Mark object that it preserved, as discussed in Listing 20-7. Then it will instantiate redoStack_, if it's not done so, and push the Command object that was popped from undoStack_ half a second ago (it shouldn't take that long, by the way).

- (void) redoCommand
{
  Command *command = [redoStack_ pop];
  [command execute];

  // push the command back to the undo stack
  [undoStack_ push:command];
}

The redoCommand is even simpler than undoCommand. The first half is almost the same as undoCommand except that the last Command object is now popped from redoStack_ instead, and command invokes execute rather than undo. After that, it pushes command back to undoStack_. So when the undoCommand method is invoked again, the process will be repeated.

So how can we connect DrawScribbleCommand objects with the actual drawing events? We are going explain with a bunch of touch event handlers defined in CanvasViewController in Listing 20-9. The mechanism of drawing different types of Mark based on the touch events are discussed in Chapter 12 as well as the previous "Modifying Touch Event Handlers for Invocations" section. So we will only highlight changes specific to our home-brewed infrastructure. Like in the last part, we will break the listing up into two chunks to discuss.

Example 20-9. Touch Event Handlers in CanvasViewController That Manipulate DrawScribbleCommand Objects

#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];

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                              scribble_, ScribbleObjectUserInfoKey,
                              newStroke, MarkObjectUserInfoKey,
                              [NSNumber numberWithBool:NO],
                              AddToPreviousMarkUserInfoKey, nil];
    DrawScribbleCommand *command = [[[DrawScribbleCommand alloc] init] autorelease];
    [command setUserInfo:userInfo];
    [self executeCommand:command prepareForUndo:YES];
  }

  // 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];
}

Now instead of using addMark:shouldAddToPreviousMark: to add a Stroke object, we use a DrawScribbleCommand object to handle that. It takes a dictionary of userInfo with values associated withthree dictionary keys, ScribbleObjectUserInfoKey, MarkObjectUserInfoKey, and AddToPreviousMarkUserInfoKey. These keys are defined in Scribble as follows:

NSString *const ScribbleObjectUserInfoKey = @"ScribbleObjectUserInfoKey";
NSString *const MarkObjectUserInfoKey = @"MarkObjectUserInfoKey";
NSString *const AddToPreviousMarkUserInfoKey = @"AddToPreviousMarkUserInfoKey";

We add scribble_, newStroke, and a BOOL value of NO as an instance of NSNumber to the dictionary with the associated keys. Then we create an instance of the DrawScribbleCommand object and initialize it with the userInfo dictionary that we've just created. Instead of executing it right away, we pass it to a CanvasViewController's instance method, executeCommand:command prepareForUndo:, to execute and prepare for undo as described in Listing 20-8. After that, the DrawScribbleCommand object is pushed to our previously defined undo stack (i.e., undoStack_), and it's ready to be undone.

- (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 lifted 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];

    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                              scribble_, ScribbleObjectUserInfoKey,
                              singleDot, MarkObjectUserInfoKey,
                              [NSNumber numberWithBool:NO],
                              AddToPreviousMarkUserInfoKey, nil];
    DrawScribbleCommand *command = [[[DrawScribbleCommand alloc] init] autorelease];
    [command setUserInfo:userInfo];
    [self executeCommand:command prepareForUndo:YES];

  }

The touchesEnd: method determines if the touch ends at the same spot where it was first landed on the screen. If so, it will create a new Dot object and attach it to the main Mark parent in scribble_. We construct a similar userInfo with the same types of elements associated with the user keys for a new DrawScribbleCommand object. Then we pass it to the executeCommand: again like drawing a stroke with vertices.

// 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
{
  // reset the start point here
  startPoint_ = CGPointZero;
}

The rest of the code is basically intact. Now you would ask, "Have we forgotten to push the operation for adding vertices into the undo stack in the touchesMoved: method?" We haven't, but we left that out for a reason. We've designed our undo infrastructure around undoing/redoing only a complete stroke or dot, not vertices. If it undoes every "step" when the user creates a stroke, the user experience as well as performance may suffer. So we keep the original addMark: message for scribble_ there.

Allowing the User to Activate Undo/Redo

Up to this point, we seem to have everything in place for our undo infrastructure to roll out. But how does the user actuate an undo/redo process in TouchPainter? On the main canvas view of TouchPainter, there are undo and redo buttons on the right-hand side of the toolbar, as shown in Figure 20-6.

A screenshot of the main canvas view showing the undo and redo buttons on the toolbar

Figure 20-6. A screenshot of the main canvas view showing the undo and redo buttons on the toolbar

Each of the buttons is tagged. When the user taps either one of them to actuate the process, we can capture and identify it in the onBarButtonHit: method, as shown in Listing 20-10.

Example 20-10. An IBAction Method for an Undo/Redo Button Hit in CanvasViewController

#pragma mark -
#pragma mark Toolbar button hit method

- (IBAction) onBarButtonHit:(id)button
{
  UIBarButtonItem *barButton = button;

  if ([barButton tag] == 4)
  {
    [self undoCommand];
  }
  else if ([barButton tag] == 5)
  {
    [self redoCommand];
  }
}

The code itself is self-explanatory based on what tag the button has to determine, whether it is for undo or redo.

For the version of NSUndoManager, the undo statement is changed to the following:

[self.undoManager undo];

as for redo:

[self.undoManager redo];

We have concluded the examples on implementing undo/redo operations in our TouchPainter. The Command pattern is commonly found in many object-oriented software designs.

What Else Can a Command Do?

The Command pattern allows executable instructions encapsulated in command objects. It makes the pattern a natural choice for implementing an undo/redo infrastructure. But it doesn't stop right there. Another well-known use of command objects is for delaying executions in an invoker. Invokers can be a menu item or a button. It's quite common to use command objects to link up operations crossing between different objects—for example, hitting a button in a view controller can execute a command object to perform some operations on another view controller. The command object hides any details related to the operations.

From the sample project of this chapter, you can find a few more Command classes used in some other areas in the TouchPainter app for different purposes. The source project files contain a lot more information than we can cover in here, so free feel to check them out and explore what you can find in them!

Summary

This chapter has introduced the Command pattern and how we can implement it in Objective-C. We have designed and implemented an undo infrastructure for the TouchPainter app so the user can undo/redo any strokes and dots drawn on the screen. We have also illustrated how the Cocoa Touch framework has adapted the pattern with different invocation and undo/redo strategies that can be applied in any iOS applications.

The benefits of decoupling between command, invoker, receiver, and client in an application are apparent. If a particular command needs to be changed in the implementation, then most of the other components in the architecture remain intact. It is very easy to add new command classes because we don't need to modify the existing classes in order to do so.

This is the end of this part about encapsulating algorithms. In the next part, we will discuss some design patterns that are related to performance and object access.



[18] 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.37.12