Register FAQ / Rules Forum Spy Search Today's Posts Mark Forums Read
Go Back   MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Aug 11, 2013, 07:11 PM   #1
DavidBlack
macrumors 6502
 
Join Date: Jan 2013
Location: Somewhere In Apple's HQ ;)
KVO Question.

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{
}
__________________
21.5"iMac late 2012, 2.7 GHz, 8 GB RAM, 1TB HD; 11.6" Macbook Air Mid 2011, 1.6 GHz, 2 GB RAM; iPhone 5 White 32GB; iPod Touch 5th gen, White, 32GB
DavidBlack is offline   0 Reply With Quote
Old Aug 11, 2013, 09:00 PM   #2
iphonedude2008
macrumors member
 
Join Date: Nov 2009
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 is offline   0 Reply With Quote
Old Aug 11, 2013, 09:31 PM   #3
DavidBlack
Thread Starter
macrumors 6502
 
Join Date: Jan 2013
Location: Somewhere In Apple's HQ ;)
Quote:
Originally Posted by iphonedude2008 View Post
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.
Thanks can you give me an example?
__________________
21.5"iMac late 2012, 2.7 GHz, 8 GB RAM, 1TB HD; 11.6" Macbook Air Mid 2011, 1.6 GHz, 2 GB RAM; iPhone 5 White 32GB; iPod Touch 5th gen, White, 32GB
DavidBlack is offline   0 Reply With Quote
Old Aug 11, 2013, 10:13 PM   #4
DavidBlack
Thread Starter
macrumors 6502
 
Join Date: Jan 2013
Location: Somewhere In Apple's HQ ;)
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.
__________________
21.5"iMac late 2012, 2.7 GHz, 8 GB RAM, 1TB HD; 11.6" Macbook Air Mid 2011, 1.6 GHz, 2 GB RAM; iPhone 5 White 32GB; iPod Touch 5th gen, White, 32GB
DavidBlack is offline   0 Reply With Quote
Old Aug 11, 2013, 10:59 PM   #5
iphonedude2008
macrumors member
 
Join Date: Nov 2009
in the class notifying

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

in the class being notified

[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(nameofmethodyouchoosetorespondthatreturnsvoidwithnoarguments) name:@"nameofnotification" object: nil];
__________________
2013 13" Macbook Air i5, 4gb, 128gb ; 13" Macbook Pro early 2011 2.3GHz 4GB RAM 320GB HDD ; iPod Touch 5 32GB ; PowerBook G4 Titanium
iphonedude2008 is offline   0 Reply With Quote
Old Aug 12, 2013, 12:09 PM   #6
smallduck
macrumors newbie
 
Join Date: Jul 2002
Quote:
Originally Posted by DavidBlack View Post
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.
smallduck is offline   0 Reply With Quote
Old Aug 12, 2013, 12:43 PM   #7
DavidBlack
Thread Starter
macrumors 6502
 
Join Date: Jan 2013
Location: Somewhere In Apple's HQ ;)
Quote:
Originally Posted by smallduck View Post
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
)
__________________
21.5"iMac late 2012, 2.7 GHz, 8 GB RAM, 1TB HD; 11.6" Macbook Air Mid 2011, 1.6 GHz, 2 GB RAM; iPhone 5 White 32GB; iPod Touch 5th gen, White, 32GB
DavidBlack is offline   0 Reply With Quote
Old Aug 12, 2013, 01:59 PM   #8
ElectricSheep
macrumors 6502
 
Join Date: Feb 2004
Location: Wilmington, DE
Send a message via AIM to ElectricSheep
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.
__________________
15'' MBP (early 2011) | i7 3770k Hackintosh | i7 Mac Mini (late 2012) | iPhone 5 | iPad 3 (2012) | iPad mini | MacOS X 10.9.2
ElectricSheep is offline   0 Reply With Quote
Old Aug 12, 2013, 02:04 PM   #9
DavidBlack
Thread Starter
macrumors 6502
 
Join Date: Jan 2013
Location: Somewhere In Apple's HQ ;)
Quote:
Originally Posted by ElectricSheep View Post
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
__________________
21.5"iMac late 2012, 2.7 GHz, 8 GB RAM, 1TB HD; 11.6" Macbook Air Mid 2011, 1.6 GHz, 2 GB RAM; iPhone 5 White 32GB; iPod Touch 5th gen, White, 32GB
DavidBlack is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Similar Threads
thread Thread Starter Forum Replies Last Post
Mophie Juice Pack Plus (iPhone 5) Question - Mute Button Question JulesK iPhone Accessories 3 Mar 30, 2014 02:58 PM
Snow Leopard File Vault Question and Basic System Password Question & Time Machine? GordonGekko999 Mac Basics and Help 0 Oct 25, 2013 06:06 AM
Question for anyone who has seen or owns the pink ipod nano? Color deciding question. Pinkstiletto66 iPod 4 Apr 4, 2013 12:57 PM
Family Share Question, Upgrade Question, Argh head hurting.. Jazwire iPhone 1 Sep 14, 2012 12:19 AM
Time Capsule - Newbie Question Wifi Question Alper1234 Mac Peripherals 1 Jun 23, 2012 09:17 AM

Forum Jump

All times are GMT -5. The time now is 09:46 AM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC