Is it possible to find out which properties were saved on a managed object after the save occurs? For example, I have someone listening for managed object context saves, (NSManagedObjectContextDidSaveNotification) and I want to know which properties on the objects were saved.
The NSManagedObjectContextDidSaveNotification does contain all three bits of information you would need to sync with a server. Check the [notification userInfo] and you will find three sets inside: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey
If you want to know what properties on an entity have changed that would require that you track them yourself using KVO. I would recommend against this as the odds of that level of effort being worth it over just pushing the entire object up to a server are slim.
Update #2
On further poking around:
From the NSManagedObjectContextWillSaveNotification you could loop through each set and reference the changedValues method. You could keep a reference to that dictionary until after you receive the NSManagedObjectContextDidSaveNotification and then process the changes. Still sounds very heavy to me.
Update
What is your end goal?!?!
If you are trying to figure out what to push to a server then being at the attribute level is too low. You should be syncing at the entity level.
If you are just trying to keep some internal consistency inside of your application then you are thinking way, way too low level. This is a solved problem. Core Data solved it.
Why don't you get them when they are about to be saved. Subscribe to NSManagedObjectContextWillSaveNotification and check insertedObjects, updatedObjects and deletedObjects of the NSManagedObjectContext.
Update:
Even easier, get the user info of the NSManagedObjectContextDidSaveNotification
From the documentation:
Typically, on thread A you register for the managed object context
save notification, NSManagedObjectContextDidSaveNotification. When you
receive the notification, its user info dictionary contains arrays
with the managed objects that were inserted, deleted, and updated on
thread B.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1
Here's the solution I settled with. I have one singleton class that is notified when a context saves. The NSManagedObjectContextWillSave notification tells me which things have changed so I store them in a dictionary with the key being the context that saved. Then when I get the NSManagedObjectContextDidSave notification, I check the dictionary for the associated context. Finally, I remove that entry from the dictionary. Does that seem reasonable?
Related
I'm having some trouble dealing with Core Data+concurrency/nested MOCs (not sure which one I'm having problems with =P).
I have a method where I pass in a managed object ID (I checked that it's permanent) and that method has a child managed object context that is confined to a certain queue. I can retrieve the object from the child managed object context via [managedObjectContext objectWithID:moID] but when I try to access any of its properties (the managed object is still a fault), I get EXC_BAD_ACCESS with the stack trace showing _svfk_1 and objc_msgSend.
I know it's kind of difficult to figure out what the problem is without sample code, but I was hoping someone could shed some light on the possible causes. Thanks. =)
EDIT: I tried using existingObjectWithID:error: instead of objectWithID: as Tom Harrington suggested and now it works sometimes but doesn't work other times. I also experienced an EXC_BAD_ACCESS crash on mergeChangesFromContextDidSaveNotification:. I suspect this could be a synchronization issue. If I edit something in one context and save while something else is edited in my child context, would that cause an issue?
EDIT 2: I figured out why existingObjectWithID:error: was working sometimes but not always. The managed object ID was indeed a temporary ID (shouldn't mergeChangesFromContextDidSaveNotification: convert it to a permanent ID?), so I had to call obtainPermanentIDsForObjects:error: before passing the ID. But I'm still getting crashes sometimes in the child context's mergeChangesFromContextDidSaveNotification:. What could be the possible causes of this? Thanks.
EDIT 3: Here's what my MOC hierarchy looks like.
Persistent Store Coordinator
|
Persistent Store MOC
/ \
Main Queue MOC Child MOC (confinement)
I'm invoking a method from the main queue that uses the Child MOC (in another queue) to insert and update some managed objects and at the same time, I'm inserting and updating managed objects in the Persistent Store MOC. Managed objects can also be updated, deleted, and inserted in the Main Queue MOC at the same time. I merge any changes from the Persistent Store Coordinator to both the Main Queue MOC and the Child MOC.
Some more questions: Does saving an MOC automatically merge things? If there is a pending merge request for an MOC and you save before that merge request is processed, can that cause issues?
EDIT 4: Here's how I initialize the Child MOC.
dispatch_sync(_searchQueue, ^{
_searchManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[_searchManagedObjectContext setParentContext:_persistentStoreManagedObjectContext];
[_searchManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
});
Btw, I notice that the merge only crashes (with EXC_BAD_ACCESS) when the notification contains deleted objects.
It looks like you're still working too hard. For your child MOC, since it's in a serial queue, use NSPrivateQueueConcurrencyType, and set its parent to your main MOC.
NSConfinementConcurrencyType is for legacy configurations.
I found a fix. Before every save, I do [moc obtainPermanentIDsForObjects:[[moc insertedObjects] allObjects] error:&error]. Now I don't get any more crashes.
I'm still a bit murky on what exactly was going on, but here's my understanding. When you save newly inserted objects, they are only assigned a permanent ID when the MOC connected to the persistent store coordinator saves. Now, either mergeChangesFromContextDidSaveNotification: propagated the permanent IDs back down (as I had expected) and some other operation just happened to occur just before the merge or there's an Apple bug somewhere. In any case, obtaining the permanent IDs beforehand solved the issue.
TL;DR Core Data+concurrency is difficult.
Prerequisites:
I have 2 methods in the network API:
return list of entities (just basic info: name, id, etc.)
return detailed info about entity
The requirement is to save only objects created by processing the second request (save to sqlite) and leave non-full objects without saving.
Also, the 'main' context should contain only full objects from 1st request, and any other 'temporary' context should contain all the others objects.
I've tried to create two instances of NSPersistentStoreCoordinator and use them for different types of contexts, but it seems that for one NSManagedObjectModel can exist only one coordinator (the pointer points to the same adress).
If I understand you correctly, then I think your best option is to only create a managed object once you're sure you want it to persist in Core Data storage. You may need another layer of non-managed objects to contain data for the "non-full" entities
(This would be something like Data Transfer Objects from Java EE programming.)
You can not save indiscriminately from within the same MOC. Saving the MOC always saves everything in it.
So, you can use a separate MOC that is never saved, and then just "move" those objects to the main MOC when they are ready to be saved.
The solution that exactly solves my problem is to create two 'forks' of core data stack:
one with default config and mainContext
the second (new instance of the same NSManagedObjectModel, new
NSPersistentStore (inMemory) and new NSPersistenStoreCoordinator )
I have an NSFetchedResultsController (FRC) backing a Table View. By definition, it's listening in for changes in it's managed object context (MOC) relevant to its defining fetch request.
While this table view is visible, I may have a small import (like ~ dozen records) I run, with managed objects that will be relevant to the watching FRC.
I run the import on a peer MOC to the one backing the table view. Both MOC's are NSMainQueueConcurrencyType, with a common parent MOC of NSPrivateQueueConcurrencyType.
As soon as the import completes with a save, my previously setup notification fires a handler, that simply does a mergeChangesFromContextDidSaveNotification:
- (void)scratchpadContextDidSave:(NSNotification *)saveNotification {
log4Info(#"Default Context now merging changes from Scratchpad Context save notification.");
[self.defaultContext mergeChangesFromContextDidSaveNotification:saveNotification];
}
Question:
How do I get the mergeChangesFromContextDidSaveNotification: to complete before any listening FRCs get notified?
Motivation
The FRC's new data coming in acts as a trigger of sorts in my application, which is fine, as long as all of the merged changes are available. My FRC acts immediately, not having complete information.
I'm going to remove logic needing the FRC to be patient this way, but it got me concerned that I'm not aware of a way to "lock" change notifications from going out until the entire merge operation is complete.
idStar,
A way to force a synchronous to all -save:s is to have them serialize their merges through a private serial queue where the last item actually performs the merge to the main MOC. Just because an item is saved to the persistent store, doesn't mean it has to be merged right away. You will have to merge the userInfo dictionaries yourself before submitting the dictionaries to the main MOC.
Andrew
I have an object - Config. I want to know if the Account attribute on Config has changed. When this happens, I want to send a NSNotification so that all code that cares about when the Account changes will know. My initial thought was in my NSManagedObject subclass that I would override the setAccount method to set a transient attribute AccountDidChange to true. Then in didSave if AccountDidChange was true I would send the notification and then set it back to false. Is there a better way? My issue though is that from what I've read, by changing AccountDidChange back to false, I would have dirtied my object and need to save again.
A little more info:
The Config object is the current configuration of the application. Account could actually be changed to ActiveAccount. There is a relationship to the Account Entity that has a list of all Accounts. The idea is that the user can change the active account of the application. So we have a set of servers and the user can only be logged into one at a time. Config.Account points to that active account and it is used to setup connections to the server to retrieve information. I am using this notification that Config.Account has changed to tell other objects to clean up their information - like list of alerts. Basically, all information is per Account so it needs to be removed and then refetched on its next load with the new active account.
Also, the given names are not my actual object names - just trying to make the example easier to follow.
Take a look at KVO (Key-Value Observing): Key-Value Observing Programming Guide. That's the standard way to do this in Cocoa, and is a fundamental technology that you need to understand to be a good Cocoa programmer.
KVO will let objects that care about changes to the Account property (which you should probably name account, not Account) register to be notified when the property is changed. KVO will "just work" for standard NSManagedObjects, without any additional work on your part.
The relevant methods are as follows:
-addObserver:forKeyPath:options:context: which you call on your Config object to set up the observation
-observeValueForKeyPath:ofObject:change:context: which will be called on the observer object anytime an observed value is changed
-removeObserver:forKeyPath: which you need to make sure you call when the observer no longer needs change notifications (including before the observer is deallocated).
This is all described in a lot more detail in the linked documentation.
EDIT BELOW:
Without knowing anything about your application, it's hard to know why you'd want to be notified only upon save. NSManagedObjectContext posts NSManagedObjectContextWillSaveNotification and NSManagedObjectContextDidSaveNotification. The notification's userInfo has arrays containing inserted, updated and deleted objects, but the notifications aren't as fine-grained as individual properties. I suppose you could manually keep track of changed accounts between didSave notifications. That'll probably get inefficient if you have lots of Configs in your store.
Changes to NSManagedObjects are immediate, they're just not saved to the persistent store until you call save: on the managed object context. Perhaps if you explain more about exactly what you're trying to accomplish and why, I can better help.
I was wondering if there is a way to share an NSManagedObject between two or more NSManagedObjectContext objects running in the same thread.
I have the following problem: I have one main context shared through all my code in the application and several different contexts that are created for each remote fetch request that I issue. (I created a custom class that fetches remotely and inserts all the objects found in the server in his own NSManagedObjectContext). Those fetch requests may run simultaneously since they use NSURLConnection objects that may end at different times. If the same remote object gets fetched by different connections, I will end up with duplicates at the moment of saving and merging the context with the main one. (That is, objects that have the same remote ID but a different objectID).
One possible solution would be to save (and so persist) every object as soon as it is created but I can't do that because it may have some relationships that may still have not been filled and won't validate during the save operation.
I'm really looking forward to a method that allows you to share the same non-persistent instance of an object between context. If anybody has encountered this issue and came up with a solution, I would be pleased to know!
Context cannot communicate between each other save through their stores. However, you can insert a managed object with a nil managed object context and it will be independent (albeit without relationships) of any context. You could pass that independent managed object around however you wished and insert it into a context when you needed to persist it. This is dangerous but possible.
However, if you're not running each connection on a separate thread then you don't gain anything by having multiple context. Each connection object will activate its delegate in sequence on the main thread. In this case, your easiest solution would be to use the same delegate for all the connections and let the delegate handle the insertions into a single context. To prevent duplication, just do a fetch on the remoteID and see if you get back an extant object before inserting a new object for that remoteID.
I don't think what you want to do is possible. I mean if you want to share changes between different contexts, you got to use notifications and merge it whenever did save or did change occur. But in your case, I'd say just use 1 context and save in the end. Or a less elegant way: save all the remote ids temporary in your app and check before inserting new ones. In this case, you can continue use multiple contexts and save after each didfinishloading.