如何改进此 objective-c 代码(块、RestKit、异步、线程)

How to improve this objective-c code (blocks, RestKit, async, threads)

我正在维护一个旧游戏代码(> 5 年),并多次更换开发人员。游戏没有专门的玩家群(早期的赌场赌博游戏)。

RestKit 用于 API 个调用。

请在下面的代码中查找评论:// SECTION_1 // SECTION_2。

// SECTION_1 : can make it async, use blocking logic. What are the some immediate risks related to introducing threading bugs?

// SECTION_2 : Need to fix a bug bug in previous logic here. Bug: self.fetchAllPlayersCallback gets invoked before waiting for self.fetchAllPlayersFriendCheckCallback. For correct UI update, I would need to combine self.fetchAllPlayersFriendCheckCallback and self.fetchAllPlayersCallback.

代码:

/* getAllPlayersInGame:(NSString *)gameId 
 *  Fetch players for a game in progress, update UI, invoke fetchAllPlayersCallback
 *  Also detect if players are friends. Prepare friends set and invoke fetchAllPlayersFriendCheckCallback.
 */  
- (void)getAllPlayersInGame:(NSString *)gameId
{
    self.fetchAllPlayersInProgress = YES;
    self.fetchAllPlayersError = nil;
    [SocialManager getPlayersAndProfilesForGameId:gameId userId:[UserManager getActiveUser] completion:^(NSError *error, SocialUsers *users, SocialProfiles *profiles) 
    {
        if (error) {
            self.fetchAllPlayersError = error;
            // TODO: show ui error alert
            return;
        }

        __block NSUInteger totalusers = [self.lobby.players count];        
        __block BOOL isAllPlayersFriends = YES;
        __block NSMutableSet *friendsInGame = [[NSMutableSet alloc] init]

        // SECTION_1
        // separate lightweight call to server per player. 
        // server implementation limitation doesn't allow sending bulk requests.            
        for (SocialUser *player in self.lobby.players) {
            NSString *playerId = player.playerID;

            [SocialManager isUser:userId friendsWithPlayer:playerId completionBlock:^(PlayHistory *playHistory, NSError *error) {
                totalusers--;                                
                if (!error) {
                    isAllPlayersFriends &= playHistory.isFriend;
                    if (playHistory.isFriend) 
                    {
                        // TODO: Add to friendsInGame
                        // TODO: save other details (game history, etc for ui population)
                    }                    
                } else {
                    self.fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
                    return;
                }

                if (0 == totalusers) {
                    fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
                }
            }];
        };

        // SECTION_2
        // TODO: update data model        
        // TODO: UI update view
        self.fetchAllPlayersInProgress = NO;
        if (self.fetchAllPlayersCallback) 
        {
            self.fetchAllPlayersCallback();
            self.fetchAllPlayersCallback = nil;
        }
    }];
}

有几种方法:

  1. 如果你有一堆可以同时发生的异步请求,并且你想在它们完成时触发一些其他任务,你可以使用 Grand Central Dispatch (GCD)派遣组。

    例如,标准的 GCD 方法不是倒数 totalUsers,而是使用调度组。调度组可以触发一些块,当一堆异步调用完成时将调用这些块。所以你:

    • 在开始循环之前创建一个组;
    • 开始异步调用前请先进入群组;
    • 将您的组留在异步调用的完成处理程序中;
    • 指定一个 dispatch_group_notify 块,当每个 "enter" 与 "leave" 匹配时将被调用。

    因此,类似于:

    dispatch_group_t group = dispatch_group_create();
    
    for (SocialUser *player in self.lobby.players) { 
        dispatch_group_enter(group);
    
        [SocialManager ...: ^{
            ...
            dispatch_group_leave(group);
        }];
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        fetchAllPlayersFriendCheckCallback(isAllPlayersFriends, friendsInGame, error);
    
        self.fetchAllPlayersInProgress = NO;
        if (self.fetchAllPlayersCallback) {
            self.fetchAllPlayersCallback();
            self.fetchAllPlayersCallback = nil;
        }
    });
    

    现在,这假定此调用是异步的,但它们可以 运行 并发地相互调用。

  2. 现在,如果这些异步调用需要连续调用(而不是同时调用),那么您可以将它们包装在异步 NSOperation 或类似的东西中,这样可以确保即使它们相对于主队列 运行 异步,它们将相对于彼此连续 运行。如果您使用该方法,而不是使用调度组来完成操作,您将使用 NSOperation 依赖项。例如,这是一个简单的例子:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    NSOperation *completion = [NSBlockOperation blockOperationWithBlock:^{
        // stuff to be done when everything else is done
    }];
    
    for (Foo *foo in self.foobars) {
        NSOperation *operation = [SocialManager operationForSomeTask:...];
        [completionOperation addDependency:operation];
        [queue addOperation:operation];
    }
    
    [[NSOperationQueue mainQueue] addOperation:completionOperation];
    

    但是所有这些都假定您重构了社交管理器以将其异步请求包装在自定义异步 NSOperation 子类中。这不是火箭科学,但如果您以前没有这样做过,您可能希望在重构现有代码之前熟悉创建它们。

  3. 前一点的另一种排列是,与其重构代码以使用自定义异步 NSOperation 子类,不如考虑像 PromiseKit 这样的框架。它仍然需要您重构您的代码,但它有一些模式可以让您将异步任务包装在 "promises"(又名 "futures")中。我只是为了完整而提及它。但是你可能不想在这个组合中加入一个全新的框架。

归根结底,这里的诊断还不够。但是分派组或自定义异步 NSOperation 具有完成操作的子类。

但是该代码中的 "use blocking logic" 注释通常不是一个好主意。您永远不应该阻塞,对于设计良好的代码,这是完全没有必要的。