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保留块 indisplayScoresWithRequest
  • displayScoresWithRequest保留中的块selfCBLeaderboardDisplay对象
  • 中的块displayScoresWithRequest保留来自的块refreshDisplay
  • refreshDisplay保留中的块selfGJGameOver对象
  • 对象GJGameOver保留,display_对象CBLeaderboardDisplay

但是,该CBLeaderboardDisplay对象不保留其实例变量request_。(这段代码写得非常糟糕,request_在方法结束时发布,但没有设置nil为一次与否。)

于 2013-03-13T05:22:10.797 回答