Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

DavidBlack

macrumors 6502a
Original poster
Jan 27, 2013
606
239
Somewhere In Apple's HQ ;)
Hi guys I have this app that when my nsuserfaults change I want to detect that change and execute some code so I came across Key-Value-Observing. It works fine and all but I need to way to observe more than one key. This is the code I am using:

Code:
[[NSUserDefaults standardUserDefaults] addObserver:self
                                        forKeyPath:@"On/Off"
                                           options:NSKeyValueObservingOptionNew
                                           context:NULL];

And this method is called whenever the user defaults is changed. Is there a way to observe multiple keys. Thanks a lot in advance.
Code:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
}
 

iphonedude2008

macrumors 65816
Nov 7, 2009
1,134
449
Irvine, CA
I just use nsnotificationcenter. Its basically the same thing, but you have to send the notification. Plus, you do have to implement a weird method or worry about thread safety since you are doing it all yourself.
 

iphonedude2008

macrumors 65816
Nov 7, 2009
1,134
449
Irvine, CA
in the class notifying

[[NSNotificationCenter defaultCenter]postNotificationName: @"nameofnotification" object: nil];

in the class being notified

[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(nameofmethodyouchoosetorespondthatreturnsvoidwithnoarguments) name:mad:"nameofnotification" object: nil];
 

smallduck

macrumors newbie
Jul 24, 2002
14
2
I did some reading on KVO and it doesn't allow passing a custom selector to be invoked. So I will have to use NSNotificationCenter.

To use NSNotificationCenter to detect changes to NSUserDefaults, you have to observe NSUserDefaultsDidChangeNotification, but there's no way to tell which of the keys was changed. See RootViewController.m in the AppPrefs sample code.

Apparently in recent iOS & OS X you can indeed use KVO to observe [NSUserDefaults standardUserDefaults] (see this StackOverflow answer). Then to solve your original question and your objection quoted above, I can't recommend highly enough the KVO-wrapper by Mike Ash, MAKVONotificationCenter. It lets you provide a custom selector (or block!) for each observation and also allows passing an array of observation key strings. Plus it eliminates the need to manually remove the observation, they get removed automatically when the either the observed or observing object is deallocated. The github repo is here, the articles about it are here and here.

Those articles don't give you a step-by-step guide for using MAKVONotificationCenter though (they're more about the details of its implementation). Here's what your setup code would be:
Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
        keyPath:userdefaultsKey
        selector:@selector(userdefaultsKey1Changed:ofObject:change:userInfo:)
        userInfo:NULL options:NSKeyValueObservingOptionNew];
or with an array of keys in place of the single key string:
Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
        keyPath:@[userdefaultsKey1, userdefaultsKey2]
        selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
        userInfo:NULL options:NSKeyValueObservingOptionNew];

And the observation method:
Code:
- (void)userdefaultsKey1or2Changed(NSString *)keyPath ofObject:(id)target change:(NSDictionary *)change userInfo:(id)userInfo {
  // do something with [[NSUserDefaults standardUserDefaults] objectForKey:keyPath]
  // or [change objectForKey:NSKeyValueChangeNewKey]
}

I'll let you figure out the blocks API yourself, except for this gotcha: Although I rarely find myself needing either option flags NSKeyValueChangeNewKey or NSKeyValueChangeOldKey, note that the block doesn't get access to the change dictionary itself. Instead, to access the NSKeyValueChangeNewKey and NSKeyValueChangeOldKey values you instead use notification.newValue and notification.oldValue, where notification is the block's parameter.
 

DavidBlack

macrumors 6502a
Original poster
Jan 27, 2013
606
239
Somewhere In Apple's HQ ;)
To use NSNotificationCenter to detect changes to NSUserDefaults, you have to observe NSUserDefaultsDidChangeNotification, but there's no way to tell which of the keys was changed. See RootViewController.m in the AppPrefs sample code.

Apparently in recent iOS & OS X you can indeed use KVO to observe [NSUserDefaults standardUserDefaults] (see this StackOverflow answer). Then to solve your original question and your objection quoted above, I can't recommend highly enough the KVO-wrapper by Mike Ash, MAKVONotificationCenter. It lets you provide a custom selector (or block!) for each observation and also allows passing an array of observation key strings. Plus it eliminates the need to manually remove the observation, they get removed automatically when the either the observed or observing object is deallocated. The github repo is here, the articles about it are here and here.

Those articles don't give you a step-by-step guide for using MAKVONotificationCenter though (they're more about the details of its implementation). Here's what your setup code would be:
Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
        keyPath:userdefaultsKey
        selector:@selector(userdefaultsKey1Changed:ofObject:change:userInfo:)
        userInfo:NULL options:NSKeyValueObservingOptionNew];
or with an array of keys in place of the single key string:
Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
        keyPath:@[userdefaultsKey1, userdefaultsKey2]
        selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
        userInfo:NULL options:NSKeyValueObservingOptionNew];

And the observation method:
Code:
- (void)userdefaultsKey1or2Changed(NSString *)keyPath ofObject:(id)target change:(NSDictionary *)change userInfo:(id)userInfo {
  // do something with [[NSUserDefaults standardUserDefaults] objectForKey:keyPath]
  // or [change objectForKey:NSKeyValueChangeNewKey]
}

I'll let you figure out the blocks API yourself, except for this gotcha: Although I rarely find myself needing either option flags NSKeyValueChangeNewKey or NSKeyValueChangeOldKey, note that the block doesn't get access to the change dictionary itself. Instead, to access the NSKeyValueChangeNewKey and NSKeyValueChangeOldKey values you instead use notification.newValue and notification.oldValue, where notification is the block's parameter.

Thanks you very much for the awesome reply you really helped me. I used your code in a test project. Here is the code I used:
Code:
#import "AppDelegate.h"
#import "MAKVONotificationCenter.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    
    static NSString* const onekey = @"test";
    static NSString* const twokey = @"test";
    NSUserDefaults* defaultsQT = [NSUserDefaults standardUserDefaults];

    
    [[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
                                                   keyPath:@[onekey, twokey]
                                                  selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
                                                  userInfo:NULL options:NSKeyValueObservingOptionNew];
    
    // Insert code here to initialize your application
}

- (void)userdefaultsKey1or2Changed:(NSString *)keyPath ofObject:(id)target change:(NSDictionary *)change userInfo:(id)userInfo {
    // do something with [[NSUserDefaults standardUserDefaults] objectForKey:keyPath]
    // or [change objectForKey:NSKeyValueChangeNewKey]
}

And here is what I got in the debug area:

Code:
2013-08-12 13:37:21.204 Ob[9305:303] -[MAKVONotificationCenter userdefaultsKey1or2Changed:ofObject:change:userInfo:]: unrecognized selector sent to instance 0x107da1ba0
2013-08-12 13:37:21.206 Ob[9305:303] -[MAKVONotificationCenter userdefaultsKey1or2Changed:ofObject:change:userInfo:]: unrecognized selector sent to instance 0x107da1ba0
2013-08-12 13:37:21.208 Ob[9305:303] (
	0   CoreFoundation                      0x0000000101a8bb06 __exceptionPreprocess + 198
	1   libobjc.A.dylib                     0x00000001005a73f0 objc_exception_throw + 43
	2   CoreFoundation                      0x0000000101b2240a -[NSObject(NSObject) doesNotRecognizeSelector:] + 186
	3   CoreFoundation                      0x0000000101a7a02e ___forwarding___ + 414
	4   CoreFoundation                      0x0000000101a79e18 _CF_forwarding_prep_0 + 232
	5   Ob                                  0x00000001000025c6 -[_MAKVONotificationHelper observeValueForKeyPath:ofObject:change:context:] + 214
	6   Foundation                          0x00000001000a07b7 NSKeyValueNotifyObserver + 390
	7   Foundation                          0x00000001000a21c1 NSKeyValueDidChange + 456
	8   Foundation                          0x000000010005d76a -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 118
	9   CoreFoundation                      0x0000000101b6d916 _CFPreferencesSetValueWithContainer + 262
	10  Foundation                          0x000000010008480d -[NSUserDefaults(NSUserDefaults) setObject:forKey:] + 90
	11  AppKit                              0x000000010076d834 -[NSUserDefaultsController _setSingleValue:forKey:] + 301
	12  Foundation                          0x00000001000e0925 -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 292
	13  AppKit                              0x0000000100796de8 -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 274
	14  AppKit                              0x0000000100796c7d -[NSBinder setValue:forBinding:error:] + 248
	15  AppKit                              0x0000000100ddf43f -[NSValueBinder _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:] + 194
	16  AppKit                              0x0000000100ddf8dc -[NSValueBinder applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 612
	17  AppKit                              0x0000000100de0633 -[NSValueBinder performAction:] + 305
	18  AppKit                              0x0000000100e1c72f -[_NSBindingAdaptor _objectDidTriggerAction:bindingAdaptor:] + 133
	19  AppKit                              0x000000010091b79a -[NSControl sendAction:to:] + 56
	20  AppKit                              0x000000010091b6eb -[NSCell _sendActionFrom:] + 138
	21  AppKit                              0x0000000100919bd3 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
	22  AppKit                              0x0000000100919421 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
	23  AppKit                              0x0000000100918b9c -[NSControl mouseDown:] + 820
	24  AppKit                              0x000000010091050e -[NSWindow sendEvent:] + 6853
	25  AppKit                              0x000000010090c644 -[NSApplication sendEvent:] + 5761
	26  AppKit                              0x000000010082221a -[NSApplication run] + 636
	27  AppKit                              0x00000001007c6bd6 NSApplicationMain + 869
	28  Ob                                  0x00000001000014e2 main + 34
	29  libdyld.dylib                       0x00000001040007e1 start + 0
)
 

ElectricSheep

macrumors 6502
Feb 18, 2004
498
4
Wilmington, DE
I quickly browsed over the source code in the git repo for MAKVONotificationCenter. From what I gather, this:

Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
                                                   keyPath:@[onekey, twokey]
                                                  selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
                                                  userInfo:NULL options:NSKeyValueObservingOptionNew];

is not the correct way to use -observeTarget:keyPath:selector:userInfo. Instead, you should probably do:

Code:
    [self observeTarget:[NSUserDefaults standardUserDefaults]
                                                   keyPath:@[onekey, twokey]
                                                  selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
                                                  userInfo:NULL options:NSKeyValueObservingOptionNew];

The reason is because -observeTarget is implemented as a category defined on NSObject (MAKVONotification). This means that every object will respond to these selectors, and its very easy to add yourself as an observer to any target.
 

DavidBlack

macrumors 6502a
Original poster
Jan 27, 2013
606
239
Somewhere In Apple's HQ ;)
I quickly browsed over the source code in the git repo for MAKVONotificationCenter. From what I gather, this:

Code:
[[MAKVONotificationCenter defaultCenter] observeTarget:[NSUserDefaults standardUserDefaults]
                                                   keyPath:@[onekey, twokey]
                                                  selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
                                                  userInfo:NULL options:NSKeyValueObservingOptionNew];

is not the correct way to use -observeTarget:keyPath:selector:userInfo. Instead, you should probably do:

Code:
    [self observeTarget:[NSUserDefaults standardUserDefaults]
                                                   keyPath:@[onekey, twokey]
                                                  selector:@selector(userdefaultsKey1or2Changed:ofObject:change:userInfo:)
                                                  userInfo:NULL options:NSKeyValueObservingOptionNew];

The reason is because -observeTarget is implemented as a category defined on NSObject (MAKVONotification). This means that every object will respond to these selectors, and its very easy to add yourself as an observer to any target.
]

Thank you so much I finally got it to work :)
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.