1

I've encountered a weird bug and would like to check if I'm using my Key value observing of changes to NSUserDefaults correctly.

I have used this code in two places in my app without issues, then I added 3rd controller that observes values for "goldCount" and "energyCount". Now when I set the initial value, the app crashes with exc_bad_access. I'm adding this controller to the view 2 seconds after it's parent view appears using performSelectorAfterDelay.

Just before displaying the game screen, I set these properties:

//crash on this line 
[[NSUserDefaults standardUserDefaults] setInteger:200 forKey: goldCount]; 

[[NSUserDefaults standardUserDefaults] setInteger:150 forKey: energyCount];

Within 3 different view controllers, I have this code in viewDidLoad:

NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults addObserver:self
           forKeyPath:@"goldCount"
              options:NSKeyValueObservingOptionNew
              context:NULL];

[defaults addObserver:self
           forKeyPath:@"energyCount"
              options:NSKeyValueObservingOptionNew
              context:NULL];

self.goldLabel.text = [NSString stringWithFormat:@"%i",[[GameDataManager sharedInstance] currentGoldCount]];
self.energyLabel.text = [NSString stringWithFormat:@"%i",[[GameDataManager sharedInstance] currentEnergyCount]];

Here's how the class updates it's labels:

// KVO handler
-(void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)anObject
                       change:(NSDictionary *)aChange context:(void *)aContext
{

    //aKeyPath gives us the name of a user default that has changed
    if([aKeyPath isEqualToString:@"goldCount"])
    {
        //we are interested in the new value
        self.goldLabel.text = [NSString stringWithFormat:@"%i",[[aChange objectForKey:@"new"] intValue]];
    }else if([aKeyPath isEqualToString:@"energyCount"])
    {
        self.energyLabel.text = [NSString stringWithFormat:@"%i",[[aChange objectForKey:@"new"] intValue]];
    }

}

After adding a call to [[NSUserDefaults standardUserDefaults] synchronize]; I get this exception the second time around:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '( ): An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. Key path: goldCount Observed object: Change: { kind = 1; new = 205; } Context: 0x0'

Ekta Padaliya
  • 5,743
  • 3
  • 39
  • 51
Alex Stone
  • 46,408
  • 55
  • 231
  • 407
  • 1
    What is `goldCount` is the first line? Shouldn't it be `@"goldCount"` ? - Are the observers removed properly (e.g. in `dealloc` ? – Martin R Jan 02 '14 at 20:00
  • static NSString* goldCount = @"goldCount"; – Alex Stone Jan 02 '14 at 20:27
  • Found the issue - I left the old code in that was instantiating the same controller the second time. It was this deallocated instance that was receiving a notification and causing the exception. More info here: http://stackoverflow.com/questions/4120539/an-observevalueforkeypathofobjectchangecontext-message-was-received-but-not – Alex Stone Jan 02 '14 at 20:31
  • @AlexStone Please understand that even if your code currently may appear to work it has issues that can break it at any given time. – Nikolai Ruhe Jan 02 '14 at 22:11

1 Answers1

8

NSUserDefaults is not documented to be KVO compliant so it's not possible to observe defaults by their key. This might be the reason for the crash but without a stack trace it's not possible to tell.

There is a notification you can register for that announces changes to the defaults system: NSUserDefaultsDidChangeNotification.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200