Safe Key Value Observing of keypaths - ios

I am trying to implement a safe key value observing on keypaths. Let's suppose that I have a data model object named person that have a workplace property. The workplace in turn have a address property that I wish to observe so I set up key value observing with the following call:
[person addObserver:theObserver
forKeyPath:#"workplace.address"
options:NSKeyValueObservingOptionNew
context:NULL];
This works fine until the person does not change workplace. As soon as this happens:
person.workplace = newWorkplace;
the KVC system crashes the application correctly saying that "oldAddress was deallocated while key value observers were still registered with it". (being oldAddress the address of the previous workplace).
Unfortunately I can not tweak the implementation of the class of 'person' object to notify the observer that workplace is going to go away. Are there any pattern to avoid this kind of crash? Maybe one can get some other notifications? How is the keypath being traversed in the case of KVC and do you have access to this chain?

Edit 2
After spending some more time with KVO, I found that in your case, you should be observing person.workplace.address instead of workplace.address. When you observe person.workplace.address, you achieve 2 things:
1) Since you owned the person object, you have absolute control over your object's life cycle. You can now removeObserver at a time of your own choosing.
2) When workplace object is changed, the KVO mechanism will "automagically" observe the new address of the new workplace. Of course, it will notify you of the new address.
Now, you can observe the address without fearing workplace being replaced at random time. This is one of the true hidden power of KVO. This allow subclasses to safely observe any superclass's objects without knowing about their life-cycles.
Edit 1
Best practice to remove an object as observer for some KVO property
The accepted answer in this thread best described your situation. You should not have observe the property address in the first place, since you have no control over the life-cycle of workplace. You have a design issue.
Original
You can observe the keyPath workplace on the person object. When this keyPath is invoked, you simply removeObserver for workplace.address.
[person addObserver:theObserver
forKeyPath:#"workplace"
options:[NSKeyValueObservingOptionNew]
context:NULL];

Related

If I am observing a collection via KVO, am I notified of changes to objects within the collection?

I assume not, but always like to double check.
Let's say I have an NSMutableArray I'm KVObserving. It holds a bunch of Employee object. If I fully assign the array, then no doubt I'll observe the change. However, what if I change one of the Employee objects references/value/objects in the array, such as employee.salary.
I assume there is no KVO notification, here correct?
And if you ever wanted something like this, how would it be achieved?
One quick nitpick: you can't be key-value observing an NSMutableArray. That's not what KVO does. What you're doing is key-value observing some property (say "employees") of some object. That property may be typed as an NSMutableArray (although that's a terrible idea) or merely backed by an NSMutableArray. But you're not observing the array. You are observing the object for changes in its employees property.
Key-value observing a collection property does not observe the properties of the objects in the collection. In general, there's no way to observe all properties, wholesale, of any object.
If you want to observe some specific property or properties of the objects in a collection, you should:
Consolidate all mutations of that collection to separate methods. You should already have done this to make your employees property KVO-compliant. In particular, I recommend that you implement the indexed collection mutating accessors.
Within those methods, use -addObserver:toObjectsAtIndexes:forKeyPath:options:context: and -removeObserver:fromObjectsAtIndexes:forKeyPath:context: to start and stop observing some key path(s) of the elements that are being added to or removed from the collection. You need to do this in the setter (-setEmployees:) for the employees property, too (stop observing all of the elements of the old array, replace it with the new array, start observing all of the elements of the new array).
Don't forget to stop observing the elements before the array is released, for example in -dealloc.
Try this :
[[employee mutableArrayValueForKey:#"salary"] addObject:...]

Remove all references to ManagedObjects belonging to a ManagedObjectContext

I'm looking to integrate iCloud with a Core-Data-managed SQLite database (only on iOS 7 and later). I've been reading Apple's guide on using Core Data with iCloud (https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingCoreDataWithiCloudPG.pdf).
To quote from the guide, "Core Data posts an NSPersistentStoreCoordinatorStoresWillChangeNotification notification. In your notification handler, you reset your managed object context and drop any references to existing managed objects."
Calling -reset on the MOC to reset it isn't the problem, the problem is the part where they say all references to managed objects need to be dropped. I understand why this needs to be done (because the persistent store is changing), what I don't know is how to do it.
All my Core Data work is handled by a singleton and I had originally thought of posting a notification, and listening classes could set all their managed objects to nil. First, this doesn't sound like a particularly good way of doing it. Secondly, I have a FetchedResultsController managing a tableView, the FetchedResultsController manages it's own managed objects, therefore, as far as I know, I can't set them to nil.
I'd be really grateful for any advice on what to do here.
Thanks in advance.
The way I handle situations like this is to post two notifications in my app: just before resetting, and just after resetting.
For example, I might post MYMainContextWillResetNotification, then reset the context, then post MYMainContextDidResetNotification.
Any controller receiving the will-reset notification should release its managed objects, but also store any information it will need to recover after the reset. Usually this will be one or more NSManagedObjectID objects. In some cases, you may not need to store anything, simply performing a fetch after the reset instead.
A typical method might look like this:
- (void)mainContextWillReset:(NSNotification *)notif
{
self->noteID = note.objectID;
}
This code supposes there is a controller for a single note object. When the reset is about to take place, the note's object identifier is stored in an instance variable.
The did-reset notification method retrieves the note.
- (void)mainContextDidReset:(NSNotification *)notif
{
note = [context existingObjectWithID:noteID error:NULL];
[self refreshViews];
}
This code uses existingObjectWithID:error:, but you could equally do a fetch.
With an NSFetchedResultsController, you would need to call performFetch: in the did-reset method, to refresh the objects.

Observing an NSNotification inside a NSManagedObject

I want my NSManagedObject to listen for a notification from a timer class that will post an NSnotification every second. This is needed to update a value in my NSManagedObject.
Problem is as the CD lifecycle is out of my control it appears that i'm getting duplicate NSNotifications which I have found out is due to the multiple contexts that a NSManagedObject could be in.
How canI listen for this notification reliably inside my NSManagedObject?
This is a normal side effect of the way Core Data works. You're creating multiple objects that represent the same underlying data. All of them are registering for the same notification, so all of them get it. Listening for a notification like this is not a very good idea, because this duplication is a fundamental part of how the system works.
If the objects that should respond to the notification all come from the same managed object context, there are workarounds. For example, to listen for the notification only if the object was fetched from the root context in a parent/child context setup, do something like
if ([[self managedObjectContext] parentContext] == nil) {
...register for notification
}
If you don't use parent/child context relationships, you could decide that one specific context is "the one" whose managed objects get the notification, and compare [self managedObjectContext] to that.
A better solution would be to sidestep the problem and listen for the notification somewhere else-- or else just update the value from the timer callback, without using a notification. Whenever the timer fires, update the value on one specific instance of the object. This way you'll know that you're making the change in exactly one place, on one object. Other instances from other contexts would need to merge the change to get the new value.

Sending NSNotifications to all objects of a class

I have an object that can be selected by a user click. With the current requirements of the app, at any time, there is no more than one of these items selected at any point during app execution.
I implemented a mechanism to enforce this, as follows:
Each of these objects has a unique identifier as a property.
When each object is created, it subscribes to the NSNotificationCenter listening for the MY_OBJECT_SELECTED notification.
When each object is selected, it posts the MY_OBJECT_SELECTED notification, with its unique Id as part of the userInfo dictionary.
Then, when each object receives the notification, it checks to see if its id is the same as the one in the userInfo. If it is, it does nothing, but if it isn't, it sets itself to unselected.
Is this a decent approach to the problem? If not, how would you do it?
It is a decent way of doing it, although it is not very efficient. The more objects you have, the more time you spend comparing IDs. The easiest way is to store your object pointers and IDs in a map table (or similar) and remember the last selected object. Whenever you select a new object, you clear the selection flag of the last selected object, then look up the new object and set its selection flag. It requires you to keep a collection of your objects, though.
The time required to update selections with this approach is independent of the number of objects you have.
If the object is spread all over the app,i.e. if it is a member in various classes. You can have a global object of same type and assign it to only that object which has been touched. In steps it will be like:
Have a global variable of object type.
At any object touch assign globalObject = currentObject;
do all operations on globalObject throughout app like calling methods and modifying object members(have a check for nil to ensure safety).
Reassign to different object with new touch.

Alternative to overriding didChangeValueForKey: on NSManagedObject

I have a managed object which acts as a playlist, it has a to-many relationship with the playlist items. There can be multiple playlists, but only one "active" playlist. The active playlist is indicated by a boolean attribute on the managed object.
I have the number of items in the active playlist displayed as the badge on a tab bar item. The view controller that the tab bar item represents is listening for a specific notification which is fired when the contents of the active playlist are updated.
I have implemented this in what I feel is a clumsy way and would like to do it better. It does work at the moment but I'm not happy with it.
Currently, each playlist object, on awakeFromFetch, checks to see if it is the active one, and if so registers itself (using key value observing) as an observer for the key path which is the key for the relationship. When it observes a change, it fires the notification, which causes the tab bar item to update itself.
If the playlist loses or gains active status it stops or starts observing itself appropriately, so notifications are only fired from the active playlist.
I would like to drop all of the self-observing KVO code as I am concerned about the various entry and exit points and when to add and remove observers appropriately. It seems too dirty.
I would like to just override didChangeValueForKey:, check and send my notification there if necessary, then call the super implementation. But this is expressly forbidden in the documentation:
didChangeValueForKey:
Invoked to inform the receiver that the value of a given property has changed.
-(void)didChangeValueForKey:(NSString *)key
Parameters
key
The name of the property that changed. Discussion For more details, see
Key-Value Observing Programming Guide.
You must not override this method.
So, what can I do?
I've just read the same documentation, but if you look at the top of the NSManagedObject documentation, it actually says "You are strongly discouraged..."
I guess it all depends on your implementation details. For example, I do the following on a data model that I can modify locally, and sync with a server:
- (void)didChangeValueForKey:(NSString *)key
{
[super didChangeValueForKey:key]; // MUST CALL THIS!
if ([key isEqualToString:NSStringFromSelector(#selector(name))] ||
[key isEqualToString:NSStringFromSelector(#selector(text))] ||
[key isEqualToString:NSStringFromSelector(#selector(filename))]
)
{
self.lastModified = [NSDate date];
}
}
I'm not sure why this would be considered bad. It's just saying "Do what you normally do. In addition, I'd like to set another property that depends on that change."
What you want is Key Value Observing. You should be able to register for that specific key, and get notified when it changes. Check this out too: Using KVO to observe changes to a property on an object inside a collection in Objective-C
I have solved this by making a separate object (a singleton I am using to manage my core data stack) the observer instead. All of the self-observing complexity has now gone and I do not need to worry about adding or removing observers on awakeFromFetch and so on.

Resources