排行榜请求,嵌套块和保留周期

时间:2013-03-12 05:25:20

标签: objective-c objective-c-blocks gamekit retain-cycle

我为我的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];
}

当图层和因此显示都被解除分配并且请求尚未完成时,我的游戏崩溃。请求完成后,它会尝试向已解除分配的实例发送消息,然后发生崩溃。是否可以取消排行榜请求?如果没有,有什么办法可以避免崩溃而不会导致内存泄漏吗?

1 个答案:

答案 0 :(得分:0)

在两个块中,您使用__block来允许块引用self而不保留它。这是问题所在,因为您正在执行异步操作,并且如果在释放self之后执行该块,则它正在使用悬空指针。保留捕获物体的块的整个点是让它们保持活着,以便块可以使用它。

制作块时不保留self通常是为了避免保留周期。但是,我在这里看不到任何保留周期:

  • request_中的displayScoresWithRequest可能会保留displayScoresWithRequest中的阻止
  • displayScoresWithRequest中的阻止selfCBLeaderboardDisplay对象
  • displayScoresWithRequest中的阻止来自refreshDisplay
  • 的阻止
  • refreshDisplay中的阻止selfGJGameOver对象
  • GJGameOver对象保留display_CBLeaderboardDisplay对象

但是,CBLeaderboardDisplay对象不保留其实例变量request_。 (这段代码编写得非常糟糕,因为request_在方法结束时被释放但未设置为nil。它应该是一个局部变量或其他东西。应该使用布尔标志如果你想检查代码是否运行过一次。)