[Command Pattern] Sử dụng NSInvocation

Command Pattern sẽ đóng gói một request hay action như là một object. Những request/action được đóng gói có thể được truyền như object, được lưu trữ, thay đổi và chứa trong queue... Apple đã implement pattern này dùng cơ chế Target-ActionInvocation.

Khởi tạo action

Đầu tiên ta cần tạo 1 toolbar tương ứng với các action: add, delete, undo2 array: 1 dùng để chứa các object Player và 1 là stack chứa các action đó. Button undo sẽ bị disable vì stack hiện tại đang empty.

    toolbar = [[UIToolbar alloc] init];
    
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    UIBarButtonItem *addItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addPlayer)];
    UIBarButtonItem *undoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction)];
    undoItem.enabled = NO;
    UIBarButtonItem *deleteItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deletePlayer)];
    
    [toolbar setItems:@[addItem,space,undoItem,space,deleteItem]];
    [self.view addSubview:toolbar];
    
    undoStack = [[NSMutableArray alloc] init];
    players = [[NSMutableArray alloc] init];

Handle action

Tiếp theo ta sẽ handle các action. Đầu tiên là action Add.

- (void)addPlayer {

    Player *player = [[Player alloc] init];
    player.name = [NSString stringWithFormat:@"player %d", players.count];
    [players addObject:player];
    [self.tableView reloadData];
}

- (void)addPlayer:(Player*)player atIndex:(int)index {
 
    [players insertObject:player atIndex:index];
    [self.tableView reloadData];
}

Kế tiếp là action Delete.

- (void)deletePlayer {
    
    NSInteger deleteIndex = 0;
    
    Player *playerDeleted = players[deleteIndex];
    
    NSMethodSignature *sig = [self methodSignatureForSelector:@selector(addPlayer:atIndex:)];
    NSInvocation *undoAction = [NSInvocation invocationWithMethodSignature:sig];
    [undoAction setTarget:self];
    [undoAction setSelector:@selector(addPlayer:atIndex:)];
    [undoAction setArgument:&playerDeleted atIndex:2];
    [undoAction setArgument:&deleteIndex atIndex:3];
    [undoAction retainArguments];
    
    [undoStack addObject:undoAction];
    
    [players removeObjectAtIndex:deleteIndex];
    [self.tableView reloadData];
    
    [toolbar.items[2] setEnabled:YES];
}

Ta sẽ xử lý xóa các player đầu tiên trong List Player. Sau đó ta định nghĩa NSMethodSignature để tạo NSInvocation sẽ dùng để reverse action delete nếu sau đó ta nhấn undo. NSInvocation cần: The selector, the target và the arguments.

  • The arguments phải được truyền bởi pointer
  • The arguments bắt đầu tại index 2; index 0 and 1 là reserved cho the target và the selector.
  • Nếu có điều kiện để the arguments deallocated, ta nên gọi retainArguments.

Cuối cùng là action Undo.

- (void)undoAction {
    
    if (undoStack.count > 0) {
        NSInvocation *undoAction = [undoStack lastObject];
        [undoStack removeLastObject];
        [undoAction invoke];
    }
    
    if (undoStack.count == 0) {
        [toolbar.items[2] setEnabled:NO];
    }
}

Undo sẽ pops last object trong stack. Object này chính là NSInvocation và có thể invoke. Gọi invoke ta sẽ tạo lại player đã bị delete và sau đó add player này trở lại list.