1

我维护一个旧的游戏代码(> 5岁),并切换开发者手几次。游戏没有专门的玩家基础(早期的赌场赌博游戏)。如何改进这个客观c代码(块,RestKit,异步,线程)

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

有几个方法:

  1. 如果你有一堆可相对于彼此同时发生异步请求的,你想引发一些其他任务完成后,您可以使用Grand Central Dispatch(GCD)调度组。

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

    • 在开始循环之前创建一个组;
    • 在开始异步调用之前输入您的组;
    • 将您的组留在异步调用的完成处理程序中;
    • 指定一个dispatch_group_notify块,当每个“输入”与“离开”匹配时将被调用。
       

    因此,像:现在

    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; 
        } 
    }); 
    

    ,这个假定这个调用是异步的,但他们可以相互并行运行。现在,如果这些异步调用需要连续调用(而不是同时调用),那么你可能会将它们包装在异步的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子类。这不是火箭科学,但如果你以前没有这样做过,那么在你重构现有的代码之前,你可能需要熟悉它们的创建。

  2. 前一点的另一种排列是不是重构你的代码中使用自定义的异步NSOperation子类,你可以考虑像PromiseKit的框架。它仍然需要你重构你的代码,但它有一些模式,可以让你将异步任务包装在“promise”(又名“futures”)中。我只提到它的完整性。但是你可能不想在这个组合中抛出一个全新的框架。

底线,这里根本没有足够的诊断。但派遣组或具有完成操作的自定义异步NSOperation子类。

但该代码中的“使用阻塞逻辑”的评论通常不是一个好主意。你永远不应该阻止和精心设计的代码,这是完全没有必要的。

+0

谢谢。现在基于你的回答重构。 – lal