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.
Related
I created UIViewController using an NSManagedObject instance, let call it A. The A has beed fetched from mainContext.
In other part of the project some kind of process have updated NSPersistedContainer using backgroundContext. During this update A changed state.
What is the best way to update UIViewController from the first paragraph. How to refetch A to update existing NSManagedObject in mainContext?
The right way to handle merging changes made on one context into another context is:
Add an observer for the NSManagedObjectContextDidSave notification.
When this notification is posted, use mergeChanges(fromContextDidSave notification: Notification) to update your context from changes made in the save operation. You can just pass the Notification along and the merge will happen, and your object will be refreshed.
An alternative is to use refresh(_ object: NSManagedObject, mergeChanges flag: Bool) for your object. Pass true for the second argument to merge in changes from the persistent store. This is probably not as good because it only affects a single object instead of everything in the context, but it's useful in some cases.
fetch in using NSFetchRequest with the predicate being the objectID! :)
the id is valid accross contextes for existing (saved) objects
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];
This is my app's processContentChanges: method, which is triggered by NSPersistentStoreDidImportUbiquitousContentChangesNotification:
- (void)processContentChanges:(NSNotification *)notification {
[self.managedObjectContext performBlock:^{
// Merge incoming data updates in the managed object context
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// Post notification to trigger UI updates
#warning What do I actually do here?
}];
}
I'm using NSFetchedResultsControllers throughout my app so that the UI updates automatically when changes are received from another device through iCloud. This all seems to work, but the comment saying // Post notification to trigger UI updates was there in the template method already. Am I actually supposed to do something here, or can I safely leave things the way they are?
Well, although I haven't had confirmation of this I don't think there is anything else that needs to be done in this method as long as the following criteria are met:
You implement NSPersistentStoreDidImportUbiquitousContentChangesNotification correctly as per the template so that new content is merged to the managed object context
Your content is generated using NSFetchedResultsController objects
Your viewControllers conforms to the the NSFetchedResultsControllerDelegate protocol, and implements controllerWillChangeContent:, controllerDidChangeContent: and controller:didChangeObject:atIndexPath:forChangeType:newIndexPath
In those methods, update your views accordingly to display new content, remove deleted content, and update changed content.
If you have any objects which utilise CoreData without an NSFetchedResultsController then you might need to update these by manually re-fetching the data when NSPersistentStoreDidImportUbiquitousContentChangesNotification is posted by CoreData.
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.
I use Core Data - and I have registered and is listening for NSManagedObjectContextDidSaveNotification:s I have a collection of data (from JSON), that I want to save, and after all objects is saved, I would like to get some kind of notification. It currently seems like this notification is sent after every object is saved. Is there some kind of built in solution for getting my desired notification? If not, how could/should I do it?
There's no built-in notification that gets posted after you've saved a specific batch of objects. Core Data really has no idea how many objects are in your collection, so it has no way to know that you've reached the end of it.
You'll get NSManagedObjectContextDidSaveNotification every time you call save: on the managed object context. You could just wait to save until you've handled all of the objects, which would mean just one NSManagedObjectContextDidSaveNotification.
A better solution is to post your own notification when you know that you've finished the collection. Define a string constant called something like JSONUpdatesCompleteNotification, and post a notification with that name after your last save: call.
NSString *JSONUpdatesCompleteNotification = #"JSONUpdatesCompleteNotification";
Then later, when you know you're done,
[[NSNotificationCenter defaultCenter] postNotificationName:JSONUpdatesCompleteNotification object:self];
Make sure you observe this notification anywhere you need to know about it, and you're done.