Life Scene analysis

Today we are going to look at command patterns. Let’s start with a real life example that makes it easier to understand. You should have used the universal remote control, which can control various brands of air conditioning or TV remote control, as long as we set the specific brand of electrical appliances on the remote control, can be remote control, you can switch to any supported brand of electrical appliances.

Today we will also make a universal remote control, as shown in the picture below:

Please ignore this strange painting style, let’s look at the specific requirements:

There are two buttons in each row to turn on and off electrical appliances respectively, except for the corresponding electrical appliances in the first three rows as shown in the figure: electric lamp, CD and refrigerator. We can turn these appliances on and off. We can arbitrarily set the electrical appliances in each row and then realize the opening and closing function of the electrical appliance. That is to say, the remote control does not care about the specific electrical appliance in each row. Users can arbitrarily set the corresponding electrical appliance in the row button, and the remote control can realize the function of opening and closing the electrical appliance.

Let’s analyze:

Our requirement is that the user can set the specific appliance corresponding to each row of buttons at will, and then press the open or close button to perform the open and close function of the appliance, and any other appliance can perform the open and close function. We use programming thinking abstract thinking, the opening and closing appliances is a request command, and the specific appliance is the executor of the command receiver, if you want to implement a request command can be arbitrary command receiver, then must to decouple the two and make requests don’t need to care about a specific command receiver and the details of the command execution, just issued a command, It can then be accepted and executed by the specific recipient of the command.

So how do you decouple them? To make both don’t know each other, so we must introduce an intermediate amount, this is the command object, it will put every button and a specific command object, command object will expose an interface used for the button, at the same time every command object associated with specific command receiver, call the function of the command receiver. Pressing the button at this point invokes the public interface of the associated command object, and then the public method of the specific command receiver (appliance) is invoked inside the command object to perform the function.

Back to the example above, the user sets the electrical appliance corresponding to each row of buttons as shown in the figure above. At this time, the user presses the open button in the first row and issues the open command, which triggers the command object associated with the button. The command object invokes the specific command receiver lamp and invokes the turn on function of the lamp. The remote control gives the command, and who executes it and how it executes it is none of its business.


Concrete code implementation

Define the public interface of the command object (actions performed by the button)

Common methods for buttons are defined here to enable the open function


#import <Foundation/Foundation.h>

@protocol CommandInterface <NSObject>
-(void)execute;
@endCopy the code

2. Define specific command objects (actions associated with each button)

Let’s implement the specific command objects for each button, which need to implement the protocol methods above, and then call the public methods of the specific command receiver to complete the function.

For example, the on button in the first row, the associated operation is to turn on the light. The other buttons are similar

#import <Foundation/Foundation.h>
#import "CommandInterface.h"
#import "Light.h"

@interface LightOnCommand : NSObject<CommandInterface>
@property(strong,nonatomic)Light *light;
-(instancetype)initWithLight:(Light *)light;
@end

===============


#import "LightOnCommand.h"

@implementation LightOnCommand
-(instancetype)initWithLight:(Light *)light{
    if (self == [super init]) {
        self.light = light;
    }

    return self;
}

-(void)execute{
    [self.light lightOn];
}

@endCopy the code
#import <Foundation/Foundation.h>
#import "CommandInterface.h"
#import "Light.h"

@interface LightOffCommand : NSObject<CommandInterface>
@property(strong,nonatomic)Light *light;
-(instancetype)initWithLight:(Light *)light;

@end

===========

#import "LightOffCommand.h"

@implementation LightOffCommand
-(instancetype)initWithLight:(Light *)light{
    if (self == [super init]) {
        self.light = light;
    }

    return self;
}
-(void)execute{
    [self.light lightOff];
}

@endCopy the code
#import <Foundation/Foundation.h> #import "CommandInterface.h" #import "CDPlayer.h" @interface CDPlayOnCommand : NSObject<CommandInterface> @property(strong,nonatomic)CDPlayer *player; -(instancetype)initWithPlayer:(CDPlayer *)player; @end =============== #import "CDPlayOnCommand.h" @implementation CDPlayOnCommand -(instancetype)initWithPlayer:(CDPlayer  *)player{ if (self == [super init]) { self.player = player; } return self; } -(void)execute{ [self.player CDOn]; [self.player setVolume:11]; } @endCopy the code
#import <Foundation/Foundation.h>
#import "CDPlayer.h"
#import "CommandInterface.h"

@interface CDPlayerOffCommand : NSObject<CommandInterface>
@property(strong,nonatomic)CDPlayer *player;
-(instancetype)initWithPlayer:(CDPlayer *)player;
@end

==========

#import "CDPlayerOffCommand.h"

@implementation CDPlayerOffCommand
-(instancetype)initWithPlayer:(CDPlayer *)player{
    if (self == [super init]) {
        self.player = player;
    }

    return self;
}
-(void)execute{
    [self.player CDOff];
    [self.player setVolume:0];
}

@endCopy the code

3. Define the receiver of the command

Each command receiver (various appliances) is the implementer of a specific function, and the command object calls the exposed methods defined here to implement the protocol’s method -excute

For example, the light on and off function, here to achieve specific

#import <Foundation/Foundation.h> @interface Light : NSObject -(void)lightOn; -(void)lightOff; @ end = = = = = = = = = = = # import "Light. H" @ implementation Light - (void) lightOn {NSLog (@ "" the Light is opened); } -(void)lightOff{NSLog(@" light is off "); } @endCopy the code
#import <Foundation/Foundation.h> @interface CDPlayer : NSObject -(void)CDOn; -(void)CDOff; -(void)setVolume:(NSInteger)volume; @end ========= #import "cdplayer. h" @implementation CDPlayer -(void)CDOn{NSLog(@"CD player is open "); } -(void)CDOff{NSLog(@"CD player is off "); } -(void)setVolume:(NSInteger)volume{NSLog(@" set sound size to: %zd",volume); } @endCopy the code

4. Set command caller (remote control)

Now we need to define the commands of each row of buttons on the remote control. We store the open and closed command objects into two different arrays respectively, and then take out the response commands from the array according to the number of rows of buttons.

A public method setCommandWithSlot is defined for the command assembler to bind a specific command object to each of his buttons.


#import <Foundation/Foundation.h>
#import "CommandInterface.h"

@interface RemoteControl : NSObject
@property(strong,nonatomic)id<CommandInterface> slot;

-(void)onButtonClickWithSlot:(NSInteger)slot;
-(void)offButtonClickWithSlot:(NSInteger)slot;

-(void)setCommandWithSlot:(NSInteger )slot onCommand:(id<CommandInterface>)onCommand offCommand:(id<CommandInterface>)offCommand;
@end

====================
#import "RemoteControl.h"
#import "noCommand.h"


@interface RemoteControl()
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *onCommandArr;
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *offCommandArr;
@end

@implementation RemoteControl
-(void)onButtonClickWithSlot:(NSInteger)slot{
    [self.onCommandArr[slot] execute];
}

-(void)offButtonClickWithSlot:(NSInteger)slot{
    [self.offCommandArr[slot] execute];
}


- (instancetype)init
{
    self = [super init];
    if (self) {
        self.onCommandArr = [NSMutableArray array];
        self.offCommandArr = [NSMutableArray array];
        self.completeCommandsArr  = [NSMutableArray array];
        id<CommandInterface>noCommands = [[noCommand alloc]init];
        for (int i = 0; i< 4; i++) {
            self.offCommandArr[i] = noCommands;
            self.onCommandArr[i] =  noCommands;
        }
        self.undoCommand = [[noCommand alloc]init];
    }
    return self;
}


-(void)setCommandWithSlot:(NSInteger )slot onCommand:(id<CommandInterface>)onCommand offCommand:(id<CommandInterface>)offCommand
{
    self.onCommandArr[slot] = onCommand;
    self.offCommandArr[slot] = offCommand;

}
@endCopy the code

Note that when we initialize our init method, we pre-store noCommand objects in both command object arrays. This is because each row of buttons should be bound to a command object by default, so that if we need to rebind a button’s command object, we can override the noCommand object. If there is no override, the default is null, so that the client will not report an error if it calls a button that is bound to the noCommand command object.

Nocommand is implemented as follows:

#import <Foundation/Foundation.h> #import "CommandInterface.h" @interface noCommand : NSObject<CommandInterface> @end ============ #import "noCommand.h" @implementation noCommand -(void)execute{ NSLog(@" There is no install command for this slot "); } @endCopy the code

Define the command assembler

We defined the command object and the command caller (remote control) through the previous steps, and now we need to install these command objects on the different buttons of the remote control.

#import <Foundation/Foundation.h>
#import "RemoteControl.h"

@interface RemoteLoader : NSObject
- (instancetype)initWithRm:(RemoteControl*)RM;
@end

============
#import "RemoteLoader.h"
#import "Light.h"
#import "LightOnCommand.h"
#import "LightOffCommand.h"
#import "CDPlayer.h"
#import "CDPlayOnCommand.h"
#import "CDPlayerOffCommand.h"
#import "macroCommand.h"


@interface RemoteLoader ()
@property(strong,nonatomic)RemoteControl *RM;
@end

@implementation RemoteLoader

- (instancetype)initWithRm:(RemoteControl*)remote
{
    self = [super init];
    if (self) {
        self.RM = remote;
        [self configSlotCommand];
    }
    return self;
}

-(void)configSlotCommand{
    Light *light = [Light new];
    LightOnCommand *lightOn = [[LightOnCommand alloc]initWithLight:light];
    LightOffCommand *LightOff = [[LightOffCommand alloc]initWithLight:light];
    [self.RM setCommandWithSlot:0 onCommand:lightOn offCommand:LightOff];

    CDPlayer *player = [CDPlayer new];
    CDPlayOnCommand *playerOn = [[CDPlayOnCommand alloc]initWithPlayer:player];
    CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc]initWithPlayer:player];
    [self.RM setCommandWithSlot:1 onCommand:playerOn offCommand:playerOff];


}


@endCopy the code

6. Client call

Now we have completed the implementation of the remote control, here we only realize the first and second row buttons, respectively the light and CD on and off operation. Let’s look at the invocation


        RemoteControl *remote = [[RemoteControl alloc]init];
        RemoteLoader *loader = [[RemoteLoader alloc]initWithRm:remote];
        [remote onButtonClickWithSlot:0];
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

        [remote offButtonClickWithSlot:0];
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

        [remote onButtonClickWithSlot:1];
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

        [remote offButtonClickWithSlot:1];
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");

        [remote onButtonClickWithSlot:3];
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");Copy the code

The output is as follows:

2016- 12- 03 13:00:18.208Command mode [37394:323525The light is turned on2016- 12- 03 13:00:18.208Command mode [37394:323525] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --2016- 12- 03 13:00:18.208Command mode [37394:323525The light is turned off2016- 12- 03 13:00:18.208Command mode [37394:323525] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --2016- 12- 03 13:00:18.208Command mode [37394:323525The CD player is started2016- 12- 03 13:00:18.208Command mode [37394:323525] Set the sound level to:11
2016- 12- 03 13:00:18.208Command mode [37394:323525] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --2016- 12- 03 13:00:18.208Command mode [37394:323525The CD player is shut down2016- 12- 03 13:00:18.208Command mode [37394:323525] Set the sound level to:0
2016- 12- 03 13:00:18.209Command mode [37394:323525] -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --2016- 12- 03 13:00:18.209Command mode [37394:323525] The slot does not have an installation command -----------------------Copy the code

If you want to change the appliances corresponding to each row of buttons, you just need to modify RemoteLoader. Suppose that the appliances corresponding to the first row of buttons are replaced with CD player and the appliances corresponding to the second row are replaced with lights

- (void)configSlotCommand{
    Light *light = [Light new];
    LightOnCommand *lightOn = [[LightOnCommand alloc]initWithLight:light];
    LightOffCommand *LightOff = [[LightOffCommand alloc]initWithLight:light];
    [self.RM setCommandWithSlot:1 onCommand:lightOn offCommand:LightOff];

    CDPlayer *player = [CDPlayer new];
    CDPlayOnCommand *playerOn = [[CDPlayOnCommand alloc]initWithPlayer:player];
    CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc]initWithPlayer:player];
    [self.RM setCommandWithSlot:0onCommand:playerOn offCommand:playerOff]; } instead: -(void)configSlotCommand{
    Light *light = [Light new];
    LightOnCommand *lightOn = [[LightOnCommand alloc]initWithLight:light];
    LightOffCommand *LightOff = [[LightOffCommand alloc]initWithLight:light];
    [self.RM setCommandWithSlot:0 onCommand:lightOn offCommand:LightOff];

    CDPlayer *player = [CDPlayer new];
    CDPlayOnCommand *playerOn = [[CDPlayOnCommand alloc]initWithPlayer:player];
    CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc]initWithPlayer:player];
    [self.RM setCommandWithSlot:1 onCommand:playerOn offCommand:playerOff];

}Copy the code

It is also very easy to replace each row of buttons with another row of buttons by creating a new command object and setting it to the corresponding row.


Execution process analysis

Let’s use UML diagram to analyze how the above process is completed.


summary

Through the example above we can see the command completely decoupling between the caller and command receiver, both don’t need to know each other, only need to connect through the command object the intermediate quantity, so, no matter how command receiver only need to change the caller command command objects can be associated with each command, any code without changing the command to the caller, While the client is programmed for the command caller, the client code does not need to be modified, only extended.


Undo actions and macro commands

Imagine coming home from work, walking in the door, turning on the light, turning on the CD player, and playing music. However, using the above code, we need to turn on one appliance after another, which is too troublesome. Is it possible to turn on the light and CD player by pressing a button? This requires the use of macro commands, which are the execution of many command objects together.

In addition, we can also achieve the command undo function, here the command undo function is relatively simple, just need to perform the opposite operation can, such as the action is to turn on the light, then the undo operation is to turn off the light.

In practice, the undo function can be complicated because you need to record the state before the command is executed, and then the undo function has to go back to the original state. We will talk about this when we talk about the memo mode

1. Implement macro commands

#import <Foundation/Foundation.h> #import "CommandInterface.h" @interface macroCommand : NSObject<CommandInterface> @property(strong,nonatomic)NSArray<id<CommandInterface>> *commandsArr; - (instancetype)initWithCommands:(NSArray<id<CommandInterface>>*)commands; @end ============ #import "macroCommand.h" @implementation macroCommand - (instancetype)initWithCommands:(NSArray<id<CommandInterface>>*)commands { self = [super init]; if (self) { self.commandsArr = commands; } return self; } -(void)execute{ for (id<CommandInterface>command in self.commandsArr) { [command execute]; }} -(void)undo{NSLog(@" macro command too lazy to write undo "); } @endCopy the code

Modify the remoteLoader class by mapping the macro command object to the third row button

- (void)configSlotCommand{
    Light *light = [Light new];
    LightOnCommand *lightOn = [[LightOnCommand alloc]initWithLight:light];
    LightOffCommand *LightOff = [[LightOffCommand alloc]initWithLight:light];
    [self.RM setCommandWithSlot:1 onCommand:lightOn offCommand:LightOff];

    CDPlayer *player = [CDPlayer new];
    CDPlayOnCommand *playerOn = [[CDPlayOnCommand alloc]initWithPlayer:player];
    CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc]initWithPlayer:player];
    [self.RM setCommandWithSlot:0 onCommand:playerOn offCommand:playerOff];

    NSArray *onCommandArr = [NSArray arrayWithObjects:lightOn,playerOn, nil];
    NSArray *offCommandArr = [NSArray arrayWithObjects:LightOff,playerOff, nil];
    macroCommand *onMacro = [[macroCommand alloc]initWithCommands:onCommandArr];
    macroCommand *offMacro = [[macroCommand alloc]initWithCommands:offCommandArr];
    [self.RM setCommandWithSlot:2 onCommand:onMacro offCommand:offMacro];

}Copy the code

2. Realize the command cancellation function

Add undo functionality to the command object interface

#import <Foundation/Foundation.h>

@protocol CommandInterface <NSObject>
-(void)execute;
-(void)undo;
@endCopy the code

Add undo to each appliance

Add the undo function to the lightOnCommand command object by doing the opposite of excute. Other command objects are similar.


#import "LightOnCommand.h"

@implementation LightOnCommand
-(instancetype)initWithLight:(Light *)light{
    if (self == [super init]) {
        self.light = light;
    }

    return self;
}

-(void)execute{
    [self.light lightOn];
}

-(void)undo{
    [self.light lightOff];
}
@endCopy the code

Add the undo function to the remoteControl

#import "RemoteControl.h"
#import "noCommand.h"


@interface RemoteControl()
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *onCommandArr;
@property(nonatomic,strong)NSMutableArray<id<CommandInterface>> *offCommandArr;
@property(nonatomic,strong)id<CommandInterface> undoCommand;
@property(nonatomic,strong)NSMutableArray <id<CommandInterface>> *completeCommandsArr;
@end

@implementation RemoteControl
-(void)onButtonClickWithSlot:(NSInteger)slot{
    [self.onCommandArr[slot] execute];
    self.undoCommand = self.onCommandArr[slot];
    [self.completeCommandsArr addObject:self.onCommandArr[slot]];
}

-(void)offButtonClickWithSlot:(NSInteger)slot{
    [self.offCommandArr[slot] execute];
    self.undoCommand = self.offCommandArr[slot];
    [self.completeCommandsArr addObject:self.offCommandArr[slot]];
}

//撤销最后一步操作
-(void)undoButtonClick{
    [self.undoCommand undo];
}

//撤销全部操作
-(void)undoAllOperation{
    for (id<CommandInterface>command in self.completeCommandsArr) {
        [command undo];
    }
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.onCommandArr = [NSMutableArray array];
        self.offCommandArr = [NSMutableArray array];
        self.completeCommandsArr  = [NSMutableArray array];
        id<CommandInterface>noCommands = [[noCommand alloc]init];
        for (int i = 0; i< 4; i++) {
            self.offCommandArr[i] = noCommands;
            self.onCommandArr[i] =  noCommands;
        }
        self.undoCommand = [[noCommand alloc]init];
    }
    return self;
}


-(void)setCommandWithSlot:(NSInteger )slot onCommand:(id<CommandInterface>)onCommand offCommand:(id<CommandInterface>)offCommand
{
    self.onCommandArr[slot] = onCommand;
    self.offCommandArr[slot] = offCommand;

}
@endCopy the code

Here is a real life example. The problem solving idea we used above is called command mode in design mode.

Let’s look at what the command mode looks like in detail


define

Encapsulate a request as an object, allowing you to parameterize clients with different requests. Queue or log requests, and support undoable actions.

Through the above case, we can conclude that the essence of command mode is to encapsulate the request into an object. Since it is an object, it can carry out a series of operations on this object, such as passing, calling, macro command, queuing and persistence.

The command pattern decouples the initiator of a command from the receiver of a command and communicates between them through command objects. This has the following benefits:

  • Encapsulating requests, the requests can be dynamically queued and persisted to meet various service requirements
  • Multiple simple commands can be combined into one macro command to simplify operations and complete complex functions
  • Good scalability. Since the initiator and receiver of a command are completely decoupled, you only need to set the corresponding command and command object to add or modify any command at this time, without modifying the original system

In a real world scenario, if you have the following requirements, then you can consider using command mode


UML structure diagram and description

Compare this picture with the execution diagram above to give you a better understanding

Note: In real development, client and Invoker can be implemented together.


motivation

Sometimes a request must be submitted to an object without knowing anything about the recipient of the request. For example, the remote control above needs to perform the operation of turning on or off an appliance, but it does not know that the command is performed by the specific appliance.

At this point we can use the command mode to accomplish this requirement. Toolbox objects can make requests to unspecified application objects by turning the request itself into an object. This object can be stored and passed around like any other object. The key to this pattern is an abstract Command class that defines an interface to perform operations. In its simplest form, an abstract Excute operation. A concrete command subclass takes the receiver as one of its instance variables and implements excute operations that refer to actions taken by the receiver. The recipient has the specific information needed to execute the request.

From the above explanation, WE believe that you have a perceptual understanding of the command pattern. Let’s take a look at two examples of the command pattern in practice to deepen our understanding.


Command logging

1. Demand analysis

You should have encountered Windows inexplicable boot can not enter the system, at this time into the safe mode, you can choose the last correct operation, and then the system began to roll back operation, and finally can normally enter the system.

A similar function can be achieved by logging commands. We can store the executed commands. When the system fails or the execution process is interrupted, we can retrieve the commands and run them again, that is, rollback.

It is also relatively simple to implement, which is to serialize executed command objects, store them, and execute them again when needed.

In iOS we can implement object serialization through NSCoding’s two protocol methods

- (void)encodeWithCoder:(NSCoder *)aCoder 
- (id)initWithCoder:(NSCoder *)aDecoderCopy the code

We will now implement the following requirement, assuming the program starts with the following command:

RemoteControl *remote = [[RemoteControl alloc]init];
        RemoteLoader *loader = [[RemoteLoader alloc]initWithRm:remote];
        // Test the command for each button
        [remote onButtonClickWithSlot:0];
        [remote offButtonClickWithSlot:0];
        [remote onButtonClickWithSlot:1];
        [remote offButtonClickWithSlot:1];
        // Test macro commands
        [remote onButtonClickWithSlot:2];
        [remote offButtonClickWithSlot:2];Copy the code

The output is as follows:

2016- 12- 04 11:57:38.505Command mode [39084:534547The light is turned on2016- 12- 04 11:57:38.505Command mode [39084:534547The light is turned off2016- 12- 04 11:57:38.505Command mode [39084:534547The CD player is started2016- 12- 04 11:57:38.505Command mode [39084:534547] Set the sound level to:11
2016- 12- 04 11:57:38.506Command mode [39084:534547The CD player is shut down2016- 12- 04 11:57:38.506Command mode [39084:534547] Set the sound level to:0
2016- 12- 04 11:57:38.506Command mode [39084:534547The light is turned on2016- 12- 04 11:57:38.507Command mode [39084:534547The CD player is started2016- 12- 04 11:57:38.507Command mode [39084:534547] Set the sound level to:11
2016- 12- 04 11:57:38.507Command mode [39084:534547The light is turned off2016- 12- 04 11:57:38.507Command mode [39084:534547The CD player is shut down2016- 12- 04 11:57:38.507Command mode [39084:534547] Set the sound level to:0Copy the code

When we kill the program and start it again, instead of executing the above code, we simply retrieve the command object we stored in advance and roll it back to reproduce the above output.

Serialize the command object and the command receiver

Here’s how to do it:

Since we need to serialize each command object and the command receiver, we’ll do it NSCoding. For convenience, I’ll split the serialization into a macro and store it in the serialObject.h file

As follows:

#define SERIALIZE_CODER_DECODER() \ \ - (id)initWithCoder:(NSCoder *)coder \ { \ NSLog(@"%s",__func__); \ Class cls = [self class]; \ while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self class]); \ unsigned int iVarCount = 0; \ unsigned int propVarCount = 0; \ unsigned int sharedVarCount = 0; \ Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ \ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* sharedVarCount = bIsSelfClass? iVarCount : propVarCount; \ \ for (int i = 0; i < sharedVarCount; i++) { \ const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \ NSString *key = [NSString stringWithUTF8String:varName]; \ id varValue = [coder decodeObjectForKey:key]; \ NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \ if (varValue && [filters containsObject:key] == NO) { \ [self setValue:varValue forKey:key]; \ } \ } \ free(ivarList); \ free(propList); \ cls = class_getSuperclass(cls); \ } \ return self; \ } \ \ - (void)encodeWithCoder:(NSCoder *)coder \ { \ NSLog(@"%s",__func__); \ Class cls = [self class]; \ while (cls ! = [NSObject class]) {/* bIsSelfClass = (CLS == [self class]); \ unsigned int iVarCount = 0; \ unsigned int propVarCount = 0; \ unsigned int sharedVarCount = 0; \ Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL; /* List of variables, including properties and private variables */ \ objc_property_t *propList = bIsSelfClass? NULL : class_copyPropertyList(cls, &propVarCount); /* sharedVarCount = bIsSelfClass? iVarCount : propVarCount; \ \ for (int i = 0; i < sharedVarCount; i++) { \ const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \ NSString *key = [NSString stringWithUTF8String:varName]; */ id varValue = [self valueForKey:key]; */ id varValue = [self Value :key]; \ NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \ if (varValue && [filters containsObject:key] == NO) { \ [coder encodeObject:varValue forKey:key]; \ } \ } \ free(ivarList); \ free(propList); \ cls = class_getSuperclass(cls); \} \}Copy the code

Where serialization is required, simply import the.h file and write SERIALIZE_CODER_DECODER()

How to serialize LightOnCommand

#import "LightOnCommand.h"
#import "serialObject.h"
#import  <objc/runtime.h>

@implementation LightOnCommand
-(instancetype)initWithLight:(Light *)light{
    if (self == [super init]) {
        self.light = light;
    }

    return self;
}

-(void)execute{
    [self.light lightOn];
}

-(void)undo{
    [self.light lightOff];
}

SERIALIZE_CODER_DECODER()

@endCopy the code

All other command objects do this to serialize, which I won’t show you here, but note that the command accepts this and the light and CDPlayer also need to be serialized, so I won’t go into this again.

3. Save the serialized command object

#import "RemoteLoader.h" #import "Light.h" #import "LightOnCommand.h" #import "LightOffCommand.h" #import "CDPlayer.h" #import "CDPlayOnCommand.h" #import "CDPlayerOffCommand.h" #import "macroCommand.h" @interface RemoteLoader () @property(strong,nonatomic)RemoteControl *RM; @property(strong,nonatomic)NSArray *completedCommandsArr; @end @implementation RemoteLoader - (instancetype)initWithRm:(RemoteControl*)remote { self = [super init]; if (self) { self.RM = remote; [self configSlotCommand]; } return self; } -(void)configSlotCommand{ Light *light = [Light new]; LightOnCommand *lightOn = [[LightOnCommand alloc]initWithLight:light]; LightOffCommand *LightOff = [[LightOffCommand alloc]initWithLight:light]; [self.RM setCommandWithSlot:0 onCommand:lightOn offCommand:LightOff]; CDPlayer *player = [CDPlayer new]; CDPlayOnCommand *playerOn = [[CDPlayOnCommand alloc]initWithPlayer:player]; CDPlayerOffCommand *playerOff = [[CDPlayerOffCommand alloc]initWithPlayer:player]; [self.RM setCommandWithSlot:1 onCommand:playerOn offCommand:playerOff]; NSArray *onCommandArr = [NSArray arrayWithObjects:lightOn,playerOn, nil]; NSArray *offCommandArr = [NSArray arrayWithObjects:LightOff,playerOff, nil]; macroCommand *onMacro = [[macroCommand alloc]initWithCommands:onCommandArr]; macroCommand *offMacro = [[macroCommand alloc]initWithCommands:offCommandArr]; [self.RM setCommandWithSlot:2 onCommand:onMacro offCommand:offMacro]; / / object serialization order, and then save self.com pletedCommandsArr = @ [lightOn, LightOff playerOn, playerOff, onMacro, offMacro]; NSData *data1 = [NSKeyedArchiver archivedDataWithRootObject:self.completedCommandsArr]; [[NSUserDefaults standardUserDefaults]setObject:data1 forKey:@"serialCommands"]; [[NSUserDefaults standardUserDefaults]synchronize]; } @endCopy the code

4. Client call

The client calls as follows:

NSData *data = [[NSUserDefaults standardUserDefaults]objectForKey:@"serialCommands"]; NSArray *commands = [NSKeyedUnarchiver unarchiveObjectWithData:data]; // Deserialize for (id<CommandInterface> command in commands) {[command execute]; } # # # # # # # # # breakpoint # # # # # # # # # # # / / test command RemoteControl * remote = [[RemoteControl alloc] init]; RemoteLoader *loader = [[RemoteLoader alloc]initWithRm:remote]; // Test the command for each button [remote onButtonClickWithSlot:0]; [remote offButtonClickWithSlot:0]; [remote onButtonClickWithSlot:1]; [remote offButtonClickWithSlot:1]; // Test macro command [remote onButtonClickWithSlot:2]; [remote offButtonClickWithSlot:2];Copy the code

When we first run the program to the breakpoint, we can see that the Commands array is empty, as shown in the figure

Since no command has been executed at this point, there is of course no command object to store. We skip the breakpoint and continue executing, at which point the output looks as shown in the requirements analysis

Now start the program again, execute to the breakpoint, and view commands as follows:

You can see that commands now contain the command object and the command receiver from the previous execution, and the output from the execution to the breakpoint is the same as shown in the requirements analysis. The command rollback is successfully implemented.


Command queuing

Since command objects can be persisted, they can also be queued. Simply put, the process of storing various command objects in queues and then distributing them to the command recipients is command queueing. Multithreading is often used to process command queues.

At one end of the queue, command objects are placed in sequence, then at the other end of the queue, multithreading pulls them out, calls its execute() method to perform the operation, completes the operation, discards them, and then invokes the next command object, and so on.

If you need to implement a fully functional queue, it can be quite cumbersome, because to ensure scheduling between multiple threads, it is easier to use arrays as queues, and then store and retrieve command objects with synchronization locks to ensure that there is no resource contention.

Implementation is relatively simple, I will not demonstrate, you can try to achieve their own.


Download the Demo

Command mode Demo download