简体   繁体   中英

NSNotification observed by released objects

See Update below... though this initially appeared to be an animation issue, it turned out that it was an issue with notifications. Beware: an NSNotification will be observed even by objects you have discarded. Be sure to removeObserver: to avoid this.

I've been using the block-based animation for the first time and run across a situation where the "completion" block seems to be getting called more than once for one firing of an "animations" block. I can't understand why this would happen, but it seems to be happening with some consistency after my game runs for a while. Here is the code in question...

- (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];
            }
        }];
    }
}

This is a recursive method that is initially launched with step = 1 and an array of "tiles" for the player's pawn to animate across. After each step is completed, the method is called recursively to take the next step. When the array runs out of tiles, the job is done. In the completed block we either take the next step or (if the animation was not finished) just jump to the last tile. Here is a log of one run through...

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

Notice that after starting the second step on line 4 the log reports both an "unfinished" completion of the first step on line 6 and then a finished second step on line 7. However, step 1 was completed on line 3. How can it be completed on both line 3 and line 6? In this case one was completed with finished = YES and the other with finished = NO, but I have also seen this run with two or more finished = YES lines logged for a single step.

What would cause something like this? I am not even sure where to begin looking for the bug, or perhaps I just don't understand the nature of the completion block for iOS animation.

The odd thing is that this code works perfectly for one "game", but as soon as the app starts a new "game" on the same run, the code starts producing more "completion" hits, the more games I start, the more "completion" hits I get per animation. It feels like some garbage left over from old games is interfering, but no leaks are visible in the static analyzer. I'm mystified.

Update and Solution

This problem was due to the fact that the old discarded game boards were still listening to notifications to move the pawns. Even though the old boards were released, they did not specifically remove the notification requests when the games ended. Since released objects are not immediately discarded by the system, those "ghost" boards were still listening for the global notification to move the pawns. And even though they were "dead" to us, they were alive enough to respond and fight over the animation of the pawns!

The solution was to have each board [[NSNotificationCenter defaultCenter] removeObserver:self] before we release it.

See the update to the question. This issue ended up being about the notifications that first called on the pawns to move being called from old released copies of our board view. Beware, notifications survive the release of objects, make sure to explicitly remove the notification requests when you are done with objects!

I'm not sure but I think this is due to the fact that you're changing the position within the same runloop as the animation is finishing, therefore interrupting the first animation. If you did this:

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

i think it would fix your issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM