NSNotification被已释放的对象所观察

4
请查看下面的更新…尽管最初这似乎是一个动画问题,但事实证明这是一个通知问题。要注意:即使您已经丢弃了对象,NSNotification 仍将被观察到。一定要removeObserver:以避免这种情况发生。
我第一次使用基于块的动画,并遇到一个情况,即“完成”块似乎会在“动画”块的一次触发中被调用多次。我不明白为什么会发生这种情况,但似乎在游戏运行一段时间后会经常发生。以下是相关代码...
- (void)player:(Player *)player takeStep:(NSInteger)step onWalk:(NSArray *)walk {
    if (step < walk.count) {
        // take this step
        NSLog(@"taking step %i", step);
        NTTileView *tile = [walk objectAtIndex:step];
        [UIView animateWithDuration:0.5 animations:^{
            NSLog(@"animating step to tile %@",tile);
            [player.pawn moveToTile:tile];
            if ([[NSUserDefaults standardUserDefaults] boolForKey:@"UseAudio"]) [boombox play];
        } completion:^(BOOL finished){
            if (finished) {
                NSLog(@"step %i done, moving on",step);
                [self player:player takeStep:step+1 onWalk:walk];
            } else {
                NSLog(@"step %i unfinished, jumping to end",step);
                NTTileView *lastTile = [walk lastObject];
                [player.pawn setCenter:lastTile.center];
            }
        }];
    }
}

这是一个递归方法,最初使用步骤=1和“tiles”数组启动,玩家的棋子可以在其中进行动画。完成每个步骤后,递归调用该方法以进行下一步。当数组用尽时,任务完成。在完成的块中,我们要么进行下一步,要么(如果动画未完成)跳转到最后一个tile。以下是一次运行的日志...

1...[79719:1be03] taking step 1
2...[79719:1be03] animating step to tile <NTTileView: 0x957cd30; baseClass = UIControl; frame = (273 260; 57 54); layer = <CALayer: 0x957cc90>>; id = 31; type = TileTypeMethods; isHub = 0; isEndSpot = 0
3...[79719:1be03] step 1 done, moving on
4...[79719:1be03] taking step 2
5...[79719:1be03] animating step to tile <NTTileView: 0x957d3b0; baseClass = UIControl; frame = (268 202; 60 59); layer = <CALayer: 0x957d2e0>>; id = 30; type = TileTypeTermsAndTheory; isHub = 0; isEndSpot = 0
6...[79719:1be03] step 1 unfinished, jumping to end
7...[79719:1be03] step 2 done, moving on
8...[79719:1be03] taking step 3
9...[79719:1be03] animating step to tile <NTTileView: 0x957dbc0; baseClass = UIControl; frame = (268 139; 59 64); layer = <CALayer: 0x957db40>>; id = 29; type = TileTypePeople; isHub = 0; isEndSpot = 0
10...[79719:1be03] step 3 done, moving on

请注意,从第二步开始的第4行之后,日志报告了第6行上第一步的“未完成”状态,然后在第7行上完成了第二步。但是,第一步已经在第3行上完成了。它怎么可能在第3行和第6行都完成了呢?在这种情况下,一个使用finished = YES完成,另一个使用finished = NO完成,但我也看到过单个步骤记录了两个或更多finished = YES的情况。
什么会导致这样的情况发生?我甚至不确定该如何查找错误,或者也许我只是不理解iOS动画的完成块的性质。
奇怪的是,这段代码对于一个“游戏”来说完全正常,但是一旦应用程序在同一次运行中启动新的“游戏”,代码就开始产生更多的“完成”命中,我每开始一个游戏,我的动画就会有更多的“完成”命中。感觉像是一些残留垃圾互相干扰,但静态分析器中看不到任何泄漏。我感到困惑。
更新和解决方案
这个问题是由于旧的废弃游戏版仍在监听移动棋子的通知而引起的。即使旧版被释放了,当游戏结束时它们没有明确地删除通知请求。由于系统不会立即丢弃已释放的对象,因此这些“幽灵”版仍在监听全局通知以移动棋子。即使它们对我们来说已经“死了”,但它们足够活跃,可以响应并争夺棋子的动画!
解决方案是在我们释放之前让每个版[[NSNotificationCenter defaultCenter] removeObserver:self]
2个回答

1
请查看问题的更新。这个问题最终是关于通知首先调用棋子移动被从我们的旧版面视图中释放的副本中调用的。请注意,通知在对象发布后仍然存在,请确保在完成对象时明确删除通知请求!

0

我不确定,但我认为这是因为您正在同一运行循环中更改位置,而此时动画正在结束,因此打断了第一个动画。如果您这样做:

[self performSelector:@selector(takeStep:) withObject:[object that describes step] afterDelay:0.0];

我认为这会解决你的问题。


在完成块中更改动画对象是允许的,事实上对于嵌套和顺序动画至关重要。请参见iOS视图动画指南中“showHide”讨论的示例。此外,从我在performSelector文档中看到的内容来看,它相当于调用选择器,因此不会改变情况。 - EFC
以防万一我错了,我尝试了一下。使用 performSelector 产生了我之前看到的完全相同的行为。事实上,我更进一步使用通知而不是 performSelector,但仍然遇到了相同的问题。 - EFC
抱歉,我没有注意到你说更多的游戏会导致更多的问题。你能否发布创建游戏并呈现此视图的代码? - Philippe Sabourin
游戏代码实际上相当复杂,如果你真的有兴趣看到它,我可能可以给你个 zip 文件,但不想在 StackOverflow 上占用太多空间。然而,静态分析器或工具并没有显示游戏代码中存在任何泄漏。但是,是的,那就是我正在寻找的地方,只是我不知道该找什么。 - EFC
请将您的项目的压缩文件发送到我的个人资料中的电子邮件地址,我会查看一下。我很想知道为什么会发生这种情况。 - Philippe Sabourin
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接