Let's say I have a key #"MyPreference", with a corresponding value stored through NSUserDefaults.
Is there a way to be notified when the value is modified?
Or could it be done through bindings? (But this case, instead of binding the value to a UI element, I wish my object to be notified of the change, so that I can perform other tasks.)
I am aware that NSUserDefaultsDidChangeNotification can be observed, but this appears to be a all-or-nothing approach, and there does not appear to be a mechanism there to get at the specific key-value-pair that was modified. (Feel free to correct.)
Spent all day looking for the answer, only to find it 10 minutes after asking the question...
Came across a solution through Key-Value-Observing:
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath:#"values.MyPreference"
options:NSKeyValueObservingOptionNew
context:NULL];
Or, more simply (per comment below):
[[NSUserDefaults standardUserDefaults] addObserver:self
forKeyPath:#"MyPreference"
options:NSKeyValueObservingOptionNew
context:NULL];
Swift:
override func viewDidLoad() {
super.viewDidLoad()
NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "THE KEY", options: NSKeyValueObservingOptions.New, context: nil)
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
// your logic
}
deinit {
NSUserDefaults.standardUserDefaults().removeObserver(self, forKeyPath: "THE KEY")
}
And Apple employee advised to use NSUserDefaultsDidChangeNotification notification over here: https://devforums.apple.com/message/237718#237718
I agree with #DenNukem. I was using the NSKeyValueObservingOptionNew. But this function started giving me the BAD_ACCESS Code=1 error wherever I used the NSUserDefault in order to save other objects. In case you are using Key-Value Observer (KVC), just be aware of the Zombie issue on NSUserDefaults.
Here is the link to the solution:
NSUserDefaults and KVO issues
Related
I have a custom NSView class defined as:
class MyView: NSView
{
var someText: NSString
override func didChangeValueForKey(key: String)
{
println( key )
super.didChangeValueForKey( key )
}
// other stuff
}
What I want to be able to do is from outside of this class change the value of someText and have didChangeValueForKey notice that someText has changed so I can, for example, set needsDisplay to true for the view and do some other work.
How an I do this?
Are you sure you need KVC for this? KVC works fine in Swift, but there’s an easier way:
var SomeText: NSString {
didSet {
// do some work every time SomeText is set
}
}
There is no KVC mechanism for this because this isn't what KVC is for.
In Objective-C, you would implement the setter explicitly (or override if the property is originally from a superclass) and do your work there.
In Swift, the proper approach is the didSet mechanism.
didChangeValueForKey() is not part of KVC, it's part of KVO (Key-Value Observing). It is not intended to be overridden. It's intended to be called when one is implementing manual change notification (as a pair with willChangeValueForKey()).
More importantly, though, there's no reason to believe that it will be called at all for a property which is not being observed by anything. KVO swizzles the class in order to hook into the setters and other mutating accessors for those properties which are actually being observed. When such a property is changed (and supports automatic change notification), KVO calls willChangeValueForKey() and didChangeValueForKey() automatically. But for non-observed properties, those methods are not called.
Finally, in some cases, such as the indexed collection mutation accessors, KVO will use different change notification methods, such as willChange(_:valuesAtIndexes:forKey:) and didChange(_:valuesAtIndexes:forKey:).
If you really don't want to use didSet for some reason, you would use KVO to observe self for changes in the someText property and handle changes in observeValueForKeyPath(_:ofObject:change:context:). But this is a bad, clumsy, error-prone, inefficient way of doing a simple thing.
KVO and didSet are not mutually exclusive:
import Foundation
class C: NSObject {
dynamic var someText: String = "" {
didSet {
print("changed to \(someText)")
}
}
}
let c = C()
c.someText = "hi" // prints "changed to hi"
class Observer: NSObject {
init(_ c: C) {
super.init()
c.addObserver(self, forKeyPath: "someText", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("observed change to \(object!.valueForKeyPath(keyPath!))")
}
}
let o = Observer(c)
c.someText = "test" // prints "changed to test" and "observed change to test"
I would add to Jaanus's answer that to make the property KVC compliant, you should declare it as dynamic var someText: NSString.
But if you don't need all the bells and whistles oh KVC, didSet is the way to go.
Update
As for didChangeValueForKey: – it is intended for the opposite, for you to notify value for key has changed (if it is not due to one of the cases covered by Foundation). You should use addObserver(_:forKeyPath:options:context:) and override observeValueForKeyPath(_:ofObject:change:context:) to be notified of changes.
Alternatively you can use one of many 3rd party solutions such as ReactiveCococa or Facebook's KVOController
//Declarations
var scoreIncrement:Int = 0
var team = ""
var userScore = Dictionary<String,Int>()
//Set Values
team = "USA"
userScore[team] = 0
//Store in NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(userScore, forKey: "userScore")
NSUserDefaults.standardUserDefaults().synchronize()
//Increment Score
scoreIncrement = 1
userScore[team]! += scoreIncrement
Above code gives 'EXC_BAD_ACCESS(code=EXC_I386_GPFLT)' error on last line of the code. However if I comment out line:
NSUserDefaults.standardUserDefaults().setObject(userScore, forKey: "userScore")
The error goes away. Any idea why this is happening, I am not even retrieving the dictionary from NSUserDefaults yet.
Solved the problem by retrieving the dictionary back out of NSUserDefaults right after saving it with:
var userScoreTemp : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("userScore")
if userScoreTemp != nil {
userScore = userScoreTemp! as Dictionary
}
I had this problem as well. It turned out to be a KVO issue. It's possible that you have deallocated an object that was observing this value and so the message was being sent and not being received as it expects to be.
My solution was to remove the observer from the associated deallocated object. Always remember if you addObserver:forKeyPath:options:context: to match it with an associating removeObserver:forKeyPath: call.
I had the same problem.
I stored String value in NSUserDefaults like:
[[NSUserDefaults standardUserDefaults] stValue:#"Rahul" forKey:#"Name"];
After a certain period it crashed at the same point, writing the same value. The cause was that after this period I registered for changes in the NSUserdefaults with code like:
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(valueChange) name:NSUserDefaultsDidChangeNotification object:nil];
This was done in an importer at initialization. After the importer was deallocaed, a new write to the user defaults was triggering the observer for an object that was gone. Crash.
The solution was of course to remove the observer .
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObserver:self forKeyPath:#"Name"];
I have these methods in a class at the moment which seem to work fine. I would like to create a subclass which inherits these methods. The problem I have is that in the third method (shiftViewUpForKeyboard) I want the if statement to be a different UITextField (mirror being the current example).
I've read that to override a method in the subclass, you have to basically copy it exactly with the new coding, but if I want to just change that small section what is the best way to do it?
Thank you in advance.
- (void) viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(shiftViewUpForKeyboard:)
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(shiftViewDownAfterKeyboard)
name: UIKeyboardWillHideNotification
object: nil];
}
- (void) viewDidDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver: self
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] removeObserver: self
name: UIKeyboardWillHideNotification
object: nil];
}
- (void) shiftViewUpForKeyboard: (NSNotification*) theNotification;
{
if(mirror.isEditing == YES)
{
CGRect keyboardFrame;
NSDictionary* userInfo = theNotification.userInfo;
keyboardSlideDuration = [[userInfo objectForKey: UIKeyboardAnimationDurationUserInfoKey] floatValue];
keyboardFrame = [[userInfo objectForKey: UIKeyboardFrameBeginUserInfoKey] CGRectValue];
UIInterfaceOrientation theStatusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if UIInterfaceOrientationIsLandscape(theStatusBarOrientation)
keyboardShiftAmount = keyboardFrame.size.width;
else
keyboardShiftAmount = keyboardFrame.size.height;
[UIView beginAnimations: #"ShiftUp" context: nil];
[UIView setAnimationDuration: keyboardSlideDuration];
self.view.center = CGPointMake( self.view.center.x, self.view.center.y - keyboardShiftAmount);
[UIView commitAnimations];
viewShiftedForKeyboard = TRUE;
}
}
In any case if you don't want to copy the full method in the subclass, and adding your little customization, the only other possible approach I see is to change the original class. To do it I can suggest two possibilities:
1)
You could create in the original class a method called:
-(UITextField *)keyboardShiftTextField
and then in this class replace the mirror.isEditing code with:
[[self keyboardShiftTextField] isEditing]
In such case the only difference between the two classes will be in the implementation of the new method, that for the original class will be:-(UITextField *)keyboardShiftTextField {
return mirror;
}
while in the subclass this return the right text field.
2)
A second approach is more elegant as it requires the definition of the delegate pattern. This requires some overhead in term of code but we'll provide you more flexibility. Besides if the only reason to make the subclass is just to override this third method, then using the delegate pattern you can avoid creating the subclass at all, as the "custom" work will be done by the delegate. If the number of methods to override is more than one, you can still use this mechanism by moving into the protocol section all the parts that need customization. This is a quite common technique for Obj-C and Cocoa, which limits the need for some classes in many cases. Typically you use a subclass when you want to provide a different functionality, but in your case you're not providing a different functionality, but just a customization for the same functionality (the view shift up).
The usual approach would be the http://en.wikipedia.org/wiki/Template_method_pattern: pull out just the bit of the method that varies between classes, make that a separate method, and override that in subclasses.
[EDITED to add ...] An alternative approach -- I can't tell whether it would work well here without seeing more of your code -- would be to make the thing that varies a parameter that's passed into the method (or select it on the basis of a parameter passed into the method, or something else of the kind). (You'd typically then use other mechanisms rather than inheritance+polymorphism to get the effect you want, of multiple things with similar behaviour: they'd be instances of the same class but fed with different data.)
Can someone help me. I have a coredata application but I need to save the objects from a fetchedResultsController into an NSDictionary to be used for sending UILocalNotifications.
Should I use an NSMutableSet, or a NSDictionary, or an array. I'm not used to using collections and I can't figure out the best way to do that.
Could you please give me clues on how to do that please ?
Thanks,
Mike
If I'm reading your question correctly, you're asking how you should pack objects into the userInfo dictionary of a UILocalNotification. Really, it's however works best for you; userInfo dictionaries are created by you and only consumed by you.
I'm not sure why you would be using an NSFetchedResultsController - that class is for managing the marshaling of managed objects between UI classes (like UITableView) efficiently, whereas here it sounds like you would be better off just getting an NSArray of results from your managedObjectContext and the corresponding request, like this:
NSError *error = nil;
NSArray *fetchedObjects = [myManagedObjectContext executeFetchRequest: myRequest error: &error];
if (array == nil)
{
// Deal with error...
}
where you have a pre-existing managed object context and request. You don't need to use an NSFetchedResultsController here.
From there, the simplest suggestion would be to build your userInfo dictionary like this:
NSDictionary* myUserInfo = [NSDictionary dictionaryWithObject: fetchedObjects forKey: #"AnythingYouWant"];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
// ... do other setup tasks ...
localNotif.userInfo = myUserInfo;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];
Then when it comes time to receive that notification, you can read that dictionary like this:
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif
{
NSArray* myFetchedObjects = [notif.userInfo objectForKey: #"AnythingYouWant"];
for(id object in myFetchedObjects)
{
// ... do other stuff ...
}
}
Now, hopefully that's clarified how the userInfo dictionary works. I don't know the details of your app, so it's hard to say, but I'm suspicious that actually passing fetched objects is NOT what you want to do here, mainly because I'm not sure that you have any guarantee that the receiving delegate method will be working with the same object context as the sending method. I would suggest perhaps putting the entity name and predicate in the dictionary and then refetching the objects at receive time with whatever the current MOC is at that moment.
Good luck!
I am implementing MKMapView based application. In that I am using an observer when we tap on a pin. the observer is code is follows,
[annView addObserver:self
forKeyPath:#"selected"
options:NSKeyValueObservingOptionNew
context:#"ANSELECTED"];
It is working as expected, but some time it is getting exception 'EXC_BAD_ACCESS'. My log is as follows and it is showing me a leaking memory. Do I need to release the server?. If I ? then where should I release this?
An instance 0x1b21f0 of class MKAnnotationView is being deallocated
while key value observers are still registered with it. Observation
info is being leaked, and may even become mistakenly attached to
some other object. Set a breakpoint on NSKVODeallocateBreak to stop
here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x11e5f0> (
<NSKeyValueObservance 0x1b1da0: Observer: 0x120f70, Key path: selected, Options: <New: YES, Old: NO, Prior: NO> Context: 0x2b588, Property: 0x1acaa0>
It is working as excepted, but some time it is getting exception 'EXC_BAD_ACCESS'. My log is as follows and it is showing me a leaking memory. …
An instance 0x1b21f0 of class MKAnnotationView is being deallocated while key value observers are still registered with it.
That's the opposite of a leak. It is being deallocated; a leak is when an object will never be deallocated.
The problem is that it's being deallocated while something else is still observing it. Anything that's still observing this object may also send it other messages later; when it does, those messages will go to a dead object (causing the crash you saw, which happened after that message) or to a different object.
If the object that is observing the MKAnnotationView is owning it and releasing it, it needs to remove itself as an observer before releasing it. If it does not own it, it probably should.
You have to stop observing the annotation view before you release it:
[annView removeObserver:self forKeyPath:#"selected"];
I know that it's quite old but I see that this code is used a lot on stackoverflow and in other repositories, and here is a solution to the problem.
You should create an NSMutableArray ivar in your view controller class in order to store a reference to your annotations view:
MyMapViewController <MKMapViewDelegate> {
NSMutableArray *annot;
}
Initialize it in your viewDidLoad and in your - (MKAnnotationView *) mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>) annotation
you should add the MKAnnotationView to the mutable array itself right before the annView addObserver code:
if(nil == annView) {
annView = [[MyAnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier];
[annot addObject:annView];
}
[annView addObserver:self
forKeyPath:#"selected"
options:NSKeyValueObservingOptionNew
context:(__bridge void *)(GMAP_ANNOTATION_SELECTED)];
Then, in your viewDidDisappear method you can iterate the array and remove manually all the observers:
//remove observers from annotation
for (MKAnnotationView *anView in annot){
[anView removeObserver:self forKeyPath:#"selected"];
}
[annot removeAllObjects];
[super viewDidDisappear:animated];