1

私は古いゲームコード(5歳以上)を維持し、開発者の手を数回切り替えました。ゲームには、専用プレーヤーベース(初期のカジノギャンブルゲーム)はありません。この目的コードを改善するには(ブロック、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)ディスパッチグループを使用することができます。

    例えば、totalUsersをカウントダウンするのではなく、標準的なGCDアプローチはディスパッチグループを使用することです。ディスパッチグループは、非同期呼び出しの束が完了したときに呼び出されるブロックをトリガできます。だから:

    • ループを開始する前にグループを作成します。
    • 非同期コールを開始する前にグループに入ってください。
    • 非同期呼び出しの完了ハンドラにグループを残します。
    • "enter"と "leave"が一致するときに呼び出される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; 
        } 
    }); 
    

    さて、これは彼らが互いに並行して実行できることを、この呼び出しが非同期であることを前提としたが。

  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のようなフレームワークを考えることができます。それでもあなたのコードをリファクタリングする必要がありますが、非同期タスクを "約束事"(別名 "先物")で包むパターンがあります。私は完全性のためにそれを言います。しかし、あなたはこのミックスの中で全く新しいフレームワークを放棄したくないかもしれません。

ここでは、これを診断するだけでは十分ではありません。ただし、グループまたはカスタム非同期のサブクラスを完了操作でディスパッチします。

しかし、「ブロッキングロジックを使用する」というコードのコメントは一般的には良い考えではありません。あなたは決してブロックするべきではありませんし、うまく設計されたコードで、それは完全に不要です。

+0

ありがとうございました。あなたの答えに基づいて今すぐリファクタリングしてください。 – lal

関連する問題