This library provides a lightweight solution for executing Commands
. They can be executed sequentially or concurrently with CommandGroups
. Because groups are Commands
as well, they can be nested into each other. This can be used for scripted code execution. See the MGCommandConfig project for more details.
- Install CocoaPods. See https://cocoapods.org
- Add the MGCommand reference to the Podfile:
platform :ios
pod 'MGCommand'
end
- Run
pod install
from the command line - Open the newly created Xcode Workspace file
- Implement your commands
There are two command types: synchronous (MGCommand
) and asynchronous (MGAsyncCommand
). Commands can then be executed all at once (MGCommandGroup
) or one after the other (MGSequentialCommandGroup
).
A synchronous command needs to implement the MGCommand
protocol. The execute
method should contain the command logic. Here is a simple print command example (taken from the example project):
@interface PrintCommand : NSObject <MGCommand>
{
NSString *_message;
}
- (id)initWithMessage:(NSString *)message;
@end
@implementation PrintCommand
- (id)initWithMessage:(NSString *)message
{
self = [super init];
if (self)
{
_message = message;
}
return self;
}
- (void)execute
{
NSLog(_message);
}
@end
Sometimes your command execution may not finish synchronously. In that case you may want to use the MGAsyncCommand
. This command will not finish until a completeHandler
method has been called. This completeHandler
method will be set by the parent CommandGroup
before execution (see next paragraph). The following command finishes after a given delay (taken from the example project):
@interface DelayCommand : NSObject <MGAsyncCommand>
{
NSTimeInterval _delayInSeconds;
}
@property (nonatomic, copy) MGCommandCompleteHandler completeHandler;
- (id)initWithDelayInSeconds:(NSTimeInterval)aDelayInSeconds;
@end
@implementation DelayCommand
- (id)initWithDelayInSeconds:(NSTimeInterval)aDelayInSeconds
{
self = [super init];
if (self)
{
_delayInSeconds = aDelayInSeconds;
}
return self;
}
- (void)execute
{
[self performSelector:@selector(finishAfterDelay)
withObject:nil
afterDelay:_delayInSeconds];
}
- (void)finishAfterDelay
{
_completeHandler();
}
@end
Commands can be added to command groups (MGCommandGroup
) which will then execute all commands concurrently. The command group itself implements the MGAsyncCommand
protocol. It will finish when all added commands have finished their execution.
MGCommandGroup *commandGroup = [[MGCommandGroup alloc] init];
[commandGroup addCommand:[[DelayCommand alloc] initWithDelayInSeconds:1]];
[commandGroup addCommand:[[DelayCommand alloc] initWithDelayInSeconds:1]];
[commandGroup addCommand:[[DelayCommand alloc] initWithDelayInSeconds:1]];
commandGroup.completeHandler = ^
{
NSLog(@"execution finished");
};
[commandGroup execute];
Before calling the execute method on the command group, you can set the completeHandler block. It will be executed when the command group execution finished. The order you add the commands determines their execution order.
Note that the completeHandlers for added asynchronous commands will be automatically set by the group. If you add only synchronous commands you don't need the completeHandler as the group will finish instantly.
In case you want to have your commands being executed one after the other you can use the MGSequentialCommandGroup
. The order you add the commands determines their execution order.
If you need to pass data between commands, you can make use of the userInfo
dictionary. The following requirements apply:
- Your command needs to implement the
@property (nonatomic) NSMutableDictionary *userInfo
- You have to use the
MGCommandGroup
orMGSequentialCommandGroup
in order to get theuserInfo
injected - Inside the commands
execute
method, you can read and write data of theuserInfo
If you nest commands or command groups, each of them will always get the same userInfo
instance injected. Never override the userInfo
object, just set data inside.
@interface TestCommand : NSObject <MGCommand>
@property (nonatomic) NSMutableDictionary *userInfo;
@end
@implementation TestCommand
- (void)execute
{
self.userInfo[@"foo"] = @"bar";
}
@end
You can add commands to a CommandGroup
or SequentialCommandGroup
and have the execute
method automatically called as soon as a command is added. In case all commands have been finished and you add another one, the command will be executed as well. This way you can enqueue commands that should run sequentially. Note, that the completeHandler is called whenever there are no more commands to be executed. Depending when you add new commands, this can cause multiple calls.
MGCommandGroup *group = [MGSequentialCommandGroup autoStartGroup];
// adding this commands starts the execution automatically
[group addCommand:[[DelayCommand alloc] initWithDelayInSeconds:1]];
// change the autostart behavior at runtime
group.autoStart = NO;
By using the provided MGBlockCommand
you can put execution logic right into a block rather then implementing a whole class:
MGSequentialCommandGroup *sequence = [[MGSequentialCommandGroup alloc] init];
[sequence addCommand:[MGBlockCommand create:^
{
NSLog(@"Awesome block command!");
}]];
[sequence addCommand:[MGBlockCommand create:^
{
NSLog(@"And the next one!");
}]];
[sequence execute];
If you want to do an asynchronous operation you should use the MGAsyncBlockCommand
instead:
[sequence addCommand:[MGAsyncBlockCommand create:^(MGCommandCompleteHandler completeHandler)
{
[Operation doSomethingAsync:^
{
completeHandler();
}];
}]];
Please note, that you have to call completeHandler()
when the block operation is done. Otherwise the execution of the MGAsyncBlockCommand
will never finish.
Commands can now be cancellable. If your Command
should be cancellable it needs to implement the MGCancellableCommand
(it derives from MGAsyncCommand
) protocol:
@interface CancellableCommand : NSObject <MGCancellableCommand>
@property (nonatomic, copy) MGCommandCompleteHandler completeHandler;
@end
@implementation CancellableCommand
- (void)execute
{
// start async process here
}
- (void)cancel
{
// cancel async process here
}
@end
Note, that you don't have to call the completeHandler
block in case of cancelation.
The MGCommandGroup
and MGSequentialCommandGroup
are cancellable commands as well. If you cancel a group, all active commands will be cancelled. The remaining commands get removed.
For backwards compatibility any command that doesn't implement the MGCancellableCommand
protocol will just continue to execute and only the enqueued commands will be removed.
MGCommandGroup *commandGroup = [MGCommandGroup autoStartGroup];
[commandGroup addCommand:command1];
[commandGroup addCommand:command2];
[commandGroup cancel];
You can find an example application in the MGCommandsExample
subfolder. To see the exection, run the application in the simulator and press the start button.
The configured command groups look like this (pseudo code):
sequential
{
DelayCommand(1)
DelayCommand(1)
DelayCommand(1)
PrintCommand("concurrent {")
concurrent
{completeHandker
DelayCommand(1)
DelayCommand(1)
DelayCommand(1)
}
PrintCommand("}")
DelayCommand(1)
DelayCommand(1)
DelayCommand(1)
MGBlockCommand("Finished")
}
0.2.1 (2013/11/07)
- [FIXED] Exception when cancelling non-async commands
0.2.0 (2013/10/07)
- [FIXED] Fixed retain cycle:
MGCommandGroup
now takes care of it's own retain/release cycle
0.1.1 (2013/05/13)
- [FIXED] MGAsyncCommand never gets released
0.1.0 (2013/04/01)
- [NEW] Using
copy
property attribute instead ofstrong
for completeHandler - [NEW] Renamed
CommandCallback
intoMGCommandCompleteHandler
- [NEW] Renamed
callback
intocompleteHandler
- [NEW] Renamed
MGBlockCommand
toMGAsyncBlockCommand
- [NEW] New
MGBlockCommand
for synchronous execution
0.0.3 (2013/02/25)
- [NEW] Cancellable commands
0.0.2 (2013/02/11)
- [NEW] Passing data between commands (userInfo)
- [NEW] Auto-start commands groups
- [NEW]
MGBlockCommand
0.0.1 (2012/12/16)
- [NEW] Synchronous commands
- [NEW] Asynchronous commands
- [NEW] Concurrent command groups
- [NEW] Sequential command groups
This library is released under the MIT licence. Contributions are more than welcome!
Also, follow me on Twitter if you like: @MattesGroeger.