1

I have a method in a view controller that sets up some notifications:

- (void)processState
{
    MYGame *game = [[MYGameManager sharedInstance] getGameAtIndex:self.indexPath.row];

    if(game)
    {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notification_gameUpdated:) name:kMYNotificationGameUpdated object:game];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notification_gameEnded:) name:kMYNotificationGameEnded object:game];
    }
}

Then there's a game updated method, which is called every so often:

- (void)notification_gameUpdated:(NSNotification *)notification
{
    MYGame *game = notification.object;
    _game_status = (game.entity.bet.isWinning) ? MYGameStatusWin : MYGameStatusLose;
}

And finally, when the game ends:

- (void)notification_gameEnded:(NSNotification *)notification
{
    MYGame *game = notification.object;

    // Clear the notifications   
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:game];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameEnded object:game];

    self.gameIsActive = NO;
}

Trouble is, that even when I remove the observers (and a breakpoint shows that this is happening), then the notification_gameUpdated: method is still being called. If I change it to

[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:nil];

This still won't clear it. But if I change it to

[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:game];

Then that does clear it. As does

[[NSNotificationCenter defaultCenter] removeObserver:self];

But I'm not keen on doing either, because I'd rather the code was clean and I don't want any "gotchas" further down the line if I need to add more observers. I've checked the rest of the code and cannot find any other classes adding observers to this object, although other view controllers do listen to the same messages.

jowie
  • 8,028
  • 8
  • 55
  • 94
  • Put a log message in `processState` to see if it's being called more than once. – Phillip Mills Apr 24 '14 at 12:26
  • I did, and it's only being called once. – jowie Apr 24 '14 at 15:47
  • 1
    by any chance, does Game override isEqual? – Sulthan Apr 24 '14 at 16:41
  • Hmmm, you could well be onto something there. The game object extends a class called BaseModel - https://github.com/nicklockwood/BaseModel - I don't know much about what this does, but it does include stuff like `encodeWithCoder:` so I'll consult with my colleague and report back! :) – jowie Apr 24 '14 at 21:06
  • I've discovered what the problem was... Method swizzling! I will mark it as the answer here. Apologies for wasting people's time! – jowie Apr 25 '14 at 09:35

3 Answers3

3

Is processState called more than once? That would explain the behavior you are seeing.

If it is, one way to fix the issue would be to always remove listeners before adding them. See e.g. this answer.

Community
  • 1
  • 1
lekksi
  • 4,868
  • 1
  • 16
  • 13
1

edit #2

try registering with object:nil and when you post the notification include the reference to game in the userInfo dictionary. then, in the receiver, you can compare against game and perform whatever action you want if it is a match. this should get you the same behavior as if you were using object:game, although it does not explain why your current implementation isn't working


when you register for notifications like this:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(notification_gameUpdated:)
                                             name:kMYNotificationGameUpdated 
                                           object:game];

the @selector will only be performed if that particular instance of game is the sender. is it possible that you're re-initializing your shared instance of game after registering? that could cause the behavior you're experiencing

try registering for notifications with object:nil and see what happens. (assuming there are not multiple games running concurrently)

Nick
  • 2,361
  • 16
  • 27
  • It's definitely the same instance, I checked the hashes. Also, I tried what you suggested and set the object to `nil` (as in the question above) and it still doesn't remove the observer. – jowie Apr 24 '14 at 13:44
  • Sorry, that was a typo! I needed to change some of the code for 'NDA' style reasons :) The constant names do actually match up in my code. – jowie Apr 24 '14 at 15:44
  • edited with another suggestion... after that i'm out of ideas – Nick Apr 24 '14 at 15:53
  • okay thanks. At least I can do it your way, although I'd prefer to not have to do any `if` statements to check the data in the notification... But other than what you suggested, my problem seems pretty odd! – jowie Apr 24 '14 at 16:09
1

So it turned out that the reason for the issue was Method Swizzling. The project I'm working on has addObserver:selector:name:object: and removeObserver:name:object: swizzled. The issue was that although addObserver has been handled correctly, removeObserver is only removing objects on specific conditions. This will obviously need to be changed...

But I post this as a warning to others... Swizzling can be dangerous to your health!

Apologies for any time wasted.

jowie
  • 8,028
  • 8
  • 55
  • 94