命令模式封装一个请求或行为作为一个对象。封装的请求比原的更加灵活,可以在对象之间传递,储存,动态修改,或放入一个队列。苹果的Target-Action调用机制已经实现了命令模式。
你可以查看跟多关于 Target-Action 的苹果官方文档, NSInvocation包含一个target对象,一个方法和一些参数。这个对象可以按需要动态修改。这是一个非常好的命令模式的列子。减少发送对象和接受对象之间的操作,直接写成一个请求或请求链。
调用之前,你需要设置取消动作的框架。所以你需要定义一个UIToolBar和NSMutableArray保存需要撤销的堆栈。
添加代码到ViewController.m 文件:
UIToolbar *toolbar; // We will use this array as a stack to push and pop operation for the undo option NSMutableArray *undoStack;
创建一个toolbar将显示新的动作按钮,以及一个数组作为命令队列。
将下面的代码添加到viewDidLoad:
toolbar = [[UIToolbar alloc] init]; UIBarButtonItem *undoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction)]; undoItem.enabled = NO; UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIBarButtonItem *delete = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteAlbum)]; [toolbar setItems:@[undoItem,space,delete]]; [self.view addSubview:toolbar]; undoStack = [[NSMutableArray alloc] init];
上面的代码创建了一个toolbar和两个按钮,它还创建一个空的撤消堆栈。这里的撤销按钮被禁用因为撤销栈为空。
将下面代码添加到ViewController.m:
- (void)viewWillLayoutSubviews { toolbar.frame = CGRectMake(0, self.view.frame.size.height-44, self.view.frame.size.width, 44); dataTable.frame = CGRectMake(0, 130, self.view.frame.size.width, self.view.frame.size.height - 200); }
在ViewController.m增加三个方法:增加、删除、撤销。
增加一个新专辑代码如下:
- (void)addAlbum:(Album*)album atIndex:(int)index { [[LibraryAPI sharedInstance] addAlbum:album atIndex:index]; currentAlbumIndex = index; [self reloadScroller]; }
在这里你添加相册,将其设置为当前专辑索引,并重新加载。
接下来就是删除方法:
- (void)deleteAlbum { // 1 Album *deletedAlbum = allAlbums[currentAlbumIndex]; // 2 NSMethodSignature *sig = [self methodSignatureForSelector:@selector(addAlbum:atIndex:)]; NSInvocation *undoAction = [NSInvocation invocationWithMethodSignature:sig]; [undoAction setTarget:self]; [undoAction setSelector:@selector(addAlbum:atIndex:)]; [undoAction setArgument:&deletedAlbum atIndex:2]; [undoAction setArgument:¤tAlbumIndex atIndex:3]; [undoAction retainArguments]; // 3 [undoStack addObject:undoAction]; // 4 [[LibraryAPI sharedInstance] deleteAlbumAtIndex:currentAlbumIndex]; [self reloadScroller]; // 5 [toolbar.items[0] setEnabled:YES]; }
这是一段新的令人激动的代码,讲解如下:
Since there’s an action in the undo stack, you need to enable the undo button.
提示: With NSInvocation, you need to keep the following points in mind: The arguments must be passed by pointer. The arguments start at index 2; indices 0 and 1 are reserved for the target and the selector. If there’s a chance that the arguments will be deallocated, then you should call retainArguments.
最后,添加撤销方法:
- (void)undoAction { if (undoStack.count > 0) { NSInvocation *undoAction = [undoStack lastObject]; [undoStack removeLastObject]; [undoAction invoke]; } if (undoStack.count == 0) { [toolbar.items[0] setEnabled:NO]; } }
这个撤销操作是取消栈里最后一个对象,This object is always of type NSInvocation and can be invoked by calling … invoke. This invokes the command you created earlier when the album was deleted, and adds the deleted album back to the album list. Since you also deleted the last object in the stack when you “popped” it, you now check to see if the stack is empty. If it is, that means there are no more actions to undo. So you disable the Undo button.
运行你的app,测试撤销功能,删除一个专辑(或两个)然后点击撤销按钮看看效果:
这也是一个很好的地方来测试是否你的相册数据保留会变化。现在,如果你删除一个专辑,app退到后台,并终止应用程序,在下次启动应用程序显示的专辑列表不会有删除专辑信息。
这里的源代码完成的项目:最后bluelibrary