KVO Question.

Discussion in 'Mac Programming' started by DavidBlack, Aug 11, 2013.

  1. macrumors 6502a

    DavidBlack

    Joined:
    Jan 27, 2013
    Location:
    Somewhere In Apple's HQ ;)
    #1
    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{
    }
     
  2. macrumors regular

    iphonedude2008

    Joined:
    Nov 7, 2009
    Location:
    Irvine, CA
    #2
    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.
     
  3. thread starter macrumors 6502a

    DavidBlack

    Joined:
    Jan 27, 2013
    Location:
    Somewhere In Apple's HQ ;)
    #3
    Thanks can you give me an example?
     
  4. thread starter macrumors 6502a

    DavidBlack

    Joined:
    Jan 27, 2013
    Location:
    Somewhere In Apple's HQ ;)
    #4
    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.
     
  5. macrumors regular

    iphonedude2008

    Joined:
    Nov 7, 2009
    Location:
    Irvine, CA
    #5
    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];
     
  6. macrumors newbie

    Joined:
    Jul 24, 2002
    #6
    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.
     
  7. thread starter macrumors 6502a

    DavidBlack

    Joined:
    Jan 27, 2013
    Location:
    Somewhere In Apple's HQ ;)
    #7
    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
    )
     
  8. macrumors 6502

    ElectricSheep

    Joined:
    Feb 18, 2004
    Location:
    Wilmington, DE
    #8
    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.
     
  9. thread starter macrumors 6502a

    DavidBlack

    Joined:
    Jan 27, 2013
    Location:
    Somewhere In Apple's HQ ;)
    #9
    ]

    Thank you so much I finally got it to work :)
     

Share This Page