1

오래된 게임 코드를 유지하고 (5 세 이상) 개발자가 손을 몇 번 바꿨습니다. 게임에는 전용 플레이어 기반 (초기 카지노 도박 게임)이 없습니다.이 objective-c 코드 (blocks, RestKit, async, threads)를 개선하는 방법

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. 당신은 서로에 대해 동시에 일어날 수있는 비동기 요청의 무리를 가지고 있고 다른 트리거하려면 작업이 완료되면 GCD (Grand Central Dispatch) 발송 그룹을 사용할 수 있습니다.

    예를 들어 totalUsers의 수가 아니라 표준 GCD 방식은 발송 그룹을 사용하는 것입니다. Dispatch 그룹은 많은 비동기 호출이 수행 될 때 호출 될 일부 블록을 트리거 할 수 있습니다. 따라서 당신 :

    • 루프를 시작하기 전에 그룹을 생성하십시오.
    • 비동기 호출을 시작하기 전에 그룹을 입력하십시오.
    • 비동기 호출의 완료 처리기에 그룹을 남겨 둡니다.
    • 각 "입력"과 "탈퇴"가 일치 할 때 호출 할 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과 같은 프레임 워크를 고려할 수 있다는 것입니다.여전히 코드를 리팩토링해야하지만, "약속"(일명 "미래")에서 비동기 태스크를 래핑 할 수있는 패턴이 있습니다. 나는 완전성의 가져 오기를 위해 그것을 언급한다. 그러나이 혼합에서 완전히 새로운 프레임 워크를 던지기를 원하지 않을 수도 있습니다.

결론을 말하면 진단하기에 충분하지 않습니다. 그러나 그룹 또는 맞춤 비동기 NSOperation 하위 클래스를 완료 작업으로 전달하십시오.

그러나 "블로킹 논리 사용"이라는 코드의 주석은 일반적으로 좋지 않습니다. 차단하지 말고 잘 설계된 코드로 완전히 불필요합니다.

+0

감사합니다. 당신의 대답에 따라 리팩토링. – lal