【问题标题】:Leaderboard Requests, Nested Blocks, and Retain Cycles排行榜请求、嵌套块和保留周期
【发布时间】:2013-03-12 05:25:19
【问题描述】:

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

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

【问题讨论】:

  • 在 ARC 上下文中,除了 __block 之外,您还需要 __weak 来消除保留周期。此外,这些都不是明确的线程安全的,这是 GameKit 对回调的要求。
  • 天哪。那么,这可能会有点毛茸茸。您的线程,因此该块需要一个 self 的副本来保留,这样就不会发生这种过早的释放事件。我会去掉块限定符,让块保留一个 self 的副本,这听起来很奇怪。
  • @CodaFi 谢谢。我自己也想过。希望dealloc仍然会发生。我必须调试和验证。
  • 或者你可以广播一个通知。没有更多的保留周期,如果 self 走上了恐龙的道路,它将是一个 No-Op(只要您在 dealloc 中取消注册)
  • @CoadFi 你能详细说明一下吗?我可以想到一个解决方案,在 dealloc 上设置一些全局变量,然后块可以读取,但这样的解决方案至少可以说是丑陋的:D

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


【解决方案1】:

在您的两个块中,您使用__block 允许块引用self 而不保留它。这就是问题所在,因为您正在执行异步操作,并且如果在释放 self 之后执行该块,则它使用的是悬空指针。方块保留它们捕获的对象的全部目的是让它们保持活动状态,以便方块可以使用它。

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

  • displayScoresWithRequest 中的request_ 可能保留了displayScoresWithRequest 中的块
  • displayScoresWithRequest 中的块保留 selfCBLeaderboardDisplay 对象
  • displayScoresWithRequest 中的块保留了来自refreshDisplay 的块
  • refreshDisplay 中的块保留 selfGJGameOver 对象
  • GJGameOver 对象保留display_CBLeaderboardDisplay 对象

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

【讨论】:

  • 我同意,如果每个块都被允许保留其各自的自我,就不会有任何保留周期。然而,我最关心的是一个反复到达游戏结束屏幕的玩家。这会导致许多请求无法得到答复(我在调试时看到过这种情况)。我通过将请求委托给持久存在于内存中的对象并广播通知来解决了这个问题。我认为“写得不好”这个词有点苛刻。我只是使用request_ 来指示是否已经存在正在执行的请求。我已经将此变量更改为 BOOL。
猜你喜欢
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多