0

iPhone ゲーム用のリーダーボード表示クラスを開発しました。このクラスには、次のインスタンス メソッドがあります。

-(void)displayScoresWithRequest:(CBLeaderboard*)request completionHandler:(void(^)())completionHandler
{
    if (request_ != nil)
        return;

    request_ = [[CBLeaderboard alloc] init];
    [request_ setCategory:[request category]];
    [request_ setPlayerScope:[request playerScope]];
    [request_ setTimeScope:[request timeScope]];
    [request_ setRange:[request range]];

    __block CBLeaderboardDisplay* blockSelf = self;
    [request_ loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
    {
        blockSelf->request_ = nil;

        NSUInteger scoresCount = [scores count];
        if (scoresCount == 0 && error != nil)
            return;

        NSMutableArray* playerIDs = [NSMutableArray array];
        for (GKScore* score in scores)
            [playerIDs addObject:[score playerID]];

        [GKPlayer loadPlayersForIdentifiers:playerIDs withCompletionHandler:^(NSArray* players, NSError* error)
        {
            if (scoresCount > [players count] && error != nil)
                return;

            [blockSelf displayScores:scores players:players];

            completionHandler();
        }];
    }];


    [request_ release];
}

ご覧のとおり、このメソッドはリーダーボード リクエストをコピーして実行し、提供された完了ハンドラを呼び出します。私のゲームのレイヤーは、このメソッドを次のように呼び出します。

-(void)refreshDisplay
{
    CBLeaderboard* request = [[CBLeaderboard alloc] init];
    [request setCategory:[[sharedGameCenterManager_ classicLeaderboard] category]];
    [request setPlayerScope:GKLeaderboardPlayerScopeFriendsOnly];
    [request setTimeScope:GKLeaderboardTimeScopeAllTime];

    static NSRange kRequestRange = NSMakeRange(1, 3);
    [request setRange:kRequestRange];

    __block GJGameOver* blockSelf = self;
    [display_ displayScoresWithRequest:request completionHandler:^
    {
        CGSize displayContentSize = [blockSelf->display_ contentSize];
        displayContentSize.width = width(blockSelf) - 2.0 * kGJLabelPadding;
        [blockSelf->display_ setContentSize:displayContentSize];

        CGFloat displayHeight =
            bottomEdge(blockSelf->multiplierLabel_) - topEdge(blockSelf->menu_) - 2.0 * kGJLabelPadding;
        CGFloat displayScoreDisplaysCount = [blockSelf->display_ scoreDisplaysCount];
        CGFloat displayLabelPadding =
            (displayHeight - [blockSelf->display_ minContentSize].height) / displayScoreDisplaysCount;
        [blockSelf->display_ setLabelPadding:MIN(floor(displayLabelPadding), kGJLabelPadding)];

        static CGFloat kFadeInDuration = 2.0;
        if ([blockSelf->display_ opacity] == 0)
            [blockSelf->display_ runAction:[CCFadeIn actionWithDuration:kFadeInDuration]];
    }];

    [request release];
}

レイヤーとディスプレイの両方の割り当てが解除され、リクエストが完了していないと、ゲームがクラッシュします。リクエストが完了すると、割り当てが解除されたインスタンスにメッセージを送信しようとし、クラッシュが発生します。リーダーボードのリクエストをキャンセルすることはできますか? そうでない場合、メモリリークを引き起こさずにクラッシュを回避する方法はありますか?

4

1 に答える 1

0

両方のブロックで、ブロックを保持せず__blockに参照できるようにするために使用します。self非同期操作を行っているため、これが問題であり、self割り当てが解除された後にブロックが実行されると、ダングリング ポインターが使用されます。キャプチャしたオブジェクトを保持するブロックの全体的なポイントは、ブロックがそれを使用できるようにそれらを生かしておくことです。

ブロックを作成するときに保持しないselfのは、通常、保持サイクルを回避するために行われます。ただし、ここには保持サイクルはありません。

  • request_in はおそらくdisplayScoresWithRequestブロックを in に保持しますdisplayScoresWithRequest
  • オブジェクトをdisplayScoresWithRequest保持selfするブロックCBLeaderboardDisplay
  • のブロックdisplayScoresWithRequestは、からのブロックを保持しますrefreshDisplay
  • オブジェクトをrefreshDisplay保持selfするブロックGJGameOver
  • オブジェクトは、オブジェクトGJGameOverを保持display_しますCBLeaderboardDisplay

ただし、CBLeaderboardDisplayオブジェクトはそのインスタンス変数を保持しませんrequest_。(このコードはrequest_、メソッドの最後に解放されますが、に設定されていないため、非常に貧弱に書かれていnilます。おそらく、ローカル変数または何かにする必要があります。コードが実行されたかどうかを確認する場合は、ブール値フラグを使用する必要があります。一度かどうか。)

于 2013-03-13T05:22:10.797 に答える