My app periodically request updates for the objects I persist with Core Data to a web service. I then need to update the objects I have in my main context (the one provided in AppDelegate by default). It is not the user who edits the objects, so I need to avoid blocking the UI, and furthermore I'm not just modifying objects information, but deleting and adding new objects if needed.
It seems that there are two options to perform updates of NSManagedObject objects: creating a "sibling" context in a private queue, and creating a child context. Reading several posts, there are more people saying that it is better to use parent/child contexts (correct me if I'm wrong), but I don't completely understand how that works. I have some questions regarding parent/child contexts:
Can a child context be in a private queue and the parent context in the main queue?
I've read somewhere something about setting a merge policy, but I didn't find examples of its use, maybe is not necessary to set a merge policy when usen parent/child contexts? When would they be set? Where can I find an example or tutorial?
If I set my private context as child of the main context, and I save the child private context, will the objects in private context "replace" the objects in the main context as I want? (including deleting the objects that are no longer present in the private context and the new ones)... I mean... does the whole child context replace the whole parent context?
Would it be better to save the private context without being a child of the main one, and then clearing and refetching all the new data in the main context?
I really need help with this issue, thanks in advance.
Yes the child can be in a private queue and the parent in main, and the opposite way is valid too.
Merge policy...
You use a merge policy object to resolve conflicts between the persistent store and in-memory versions of managed objects.
So you would most often use this when you have multiple contexts coming directly from the persistentStoreCoordinator, perhaps one Private Queue type and one Main Queue type.
The merge policy affects the result of calling
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification
So if your deleting actions were done in the Private Queue and then saved, you could reflect that change in your UI by calling mergeChanges... on your Main context with the notification object from the
NSManagedObjectContextDidSaveNotification that the save on the Private context would create.
The parent context isn't replaced. Simply, the changes made in the child are overlaid onto the parent. So in your case deleting an object in the child would cause that object to delete once you saved the child.
No. You'd be best to do what was described in 2 if you didn't want to use parent - child contexts. What you describe is discarding your current Main context and creating a new one to reflect the new state of the PSC. Its not awful but it is expensive and if your UI has any tight binding to managed objects you need to make sure those objects are reset also. Its cheaper just merge the save.
Using mergeChangesFromContextDidSaveNotification: has been measured to be quicker than parent->child for change propagation but possibly you may find parent->child to be clearer in a readability sense.
Related
I have a scenario where an Entity has many relationships with other entities. I did some changes in the NSManagedObject of the entity and discarded those changes.
Right now I'm calling managedObjectContext.refresh(entity, mergeChanges: false) and then managedObjectContext.refresh(relatedEntity, mergeChanges: false) on every related entity to ensure having no dangling objects in the context.
What would be the difference if I directly call managedObjectContext.reset()? Should I still need to refresh or mark nil the related entities?
Is there any way to make this code more optimized?
If you call reset, you need to also immediately stop using every managed object you have fetched from that context. All fetches need to be redone after the reset, because it makes the context forget about everything it has already fetched.
There are various patterns for more efficiently creating discardable changes like you describe. One popular choice is:
Create a new managed object context and make it a child context of the current context.
Make your changes in that context.
If you want to save changes, save them. If not, just don't bother. The child context will be deallocated and its changes will be lost.
I have a model some instances of which I need to persist. Only some, not all, since persisting all instances would be wasteful. The model has primaryKey of type Int
I need to be able to pass all objects from background to main thread since Realm objects can only be used by the thread on which they were created. Current version of Realm Swift (0.94) does not seem to support handing the object over to another thread directly.
For persisted objects (the ones saved to storage with write) this is not a problem, I can fetch the object on another thread by primaryKey.
Unpersisted objects, however, are problematic. When I create a new object with the same primaryKey in background (I suppose it should be treated as the same object since it has the same primaryKey) and try to fetch it on the main thread (without persisting changes with write since I don't want in in the storage), I seem to get the old object.
I see the following solutions to this problem:
1) persist all objects (which is undesirable and otherwise unnecessary)
2) keep separate model for the objects I want to persist (leads to code duplication).
3) use init(value: AnyObject) initializer which creates unpersisted object from a Dictionary<String, AnyObject> (would probably require manual copying of object properties to dictionary, tedious and potentially error-prone)
Are there any better solutions?
Offtopic: I haven't tried Realm for Android, is situation any better with it?
You are free to pass unpersisted objects between threads as you wish -- only persisted objects cannot yet be handed over between different threads.
I think your problem is that you are creating two objects that you want to be the same object and there is no way the system can know which one you want.
The solution is as simple as it is generic: create a new object only after checking that its unique attribute does not exist already. This should work equally well with persistent and non persistent objects. Obviously, you need to have a central, thread safe in-memory repository where you can go and create new objects.
You write:
I seem to get the old object.
There should not be any old object if you have checked before.
I was wondering if someone would be able to answer this question. I am currently building my application using the CoreData stack as described by Marcus Zarra in his blog http://martiancraft.com/blog/2015/03/core-data-stack/. He describes the usage of managedObjectContext as the Single Source Of Truth where all insertions/updates/deletions should be done on this context. No exceptions. Curiously then, since this context is a child context to the private context, if I keep inserting new NSManagedObjects into managedObjectContext... wouldn't this context be filled with temporaryObjectIDs since the parent does not refresh the child context? I ask this question because how would I retrieve this NSManagedObject back from the PSC using the NSManagedObjectID if I only have the temporary one? Would I have to explicitly throw out another performBlock using the privateContext to fetch for it? That feels like a very inelegant of a solution.
You can force the conversion to happen early, before you've saved changes, like so:
[moc obtainPermanentIDsForObjects:moc.insertedObjects.allObjects error:&error];
As I understand it, the object is already in both contexts. You don't need to retrieve it from the store, as it's cached.
You still have the object and could query the store, based on the object's attributes. The only need for the ID was if you needed to pass it to another thread, but you only have the one Single Source Of Truth. What other thread is there?
Update:
After discussing your app, I would suggest not to add complexity if there is no need for complexity. You already have an API thread. Don't try to prematurely optimize it by splitting an API task into multithreaded sub tasks. You're adding overhead and the unnecessary burden of passing IDs between private contexts.
Treat what the background API is doing as a single operation, and let it download, insert, save, then notify the SSoT, which can then (re)fetch.
If it is responsive enough, you're done. You have a simple, contained approach, with no need to pass IDs.
In general, don't optimize unless there's a need for it.
I am also interested in understating how iOS 5+ parent contexts work. I am using RestKit 0.2x but I think these are general Core Data questions.
Let's say we have this store coordinator/contexts hierarchy:
Persistent Store Coordinator (PSC) -> PS Managed Object Context (PS-MOC) -> Main Queue MOC (MQ-MOC).
For a request RestKit creates a temporary Private Context (P-MOC) from MQ-MOC and on success by default saves the changes back all the way to the PSC:
PSC -> PS-MOC -> MQ-MOC [-> P-MOC]
I also manually create managed objects and modify properties directly on the MQ-MOC.
My questions:
Are my unsaved MQ-MOC changes passed to child P-MOQ when created?
Should I save my MQ-MOC changes before a child P-MOQ gets created? Should I save these changes all the way to the PSC?
If P-MOQ is configured to save only to its parent MQ-MOC context, the newly introduced changes look like my MQ-MOC unsaved changes?
Are my unsaved MQ-MOC changes passed to (an existing) child P-MOQ?
Unsaved, never. Saved, no, because they aren't required there so RestKit doesn't check and merge the changes.
How about unsaved changes when the child P-MOQ is created?
Anything saved in the parent when the child is created will be available in the child.
Should I save my MQ-MOC changes before a child P-MOQ gets created?
If the child needs them, yes (though in theory RestKit should detect that an object you use in a request isn't saved and save it for you, best not to rely on that).
Should I save these changes all the way to the PSC?
Yes. Having things saved but not persisted will just lead to issues in the future.
If P-MOQ is configured to save only to its parent MQ-MOC context, the newly introduced changes look like my MQ-MOC unsaved changes?
A child will push changes up to its direct parent but no further, so in effect, yes.
Just to check, you should be aware of the RestKit added method saveToPersistentStore:.
It seems the new queue based MOC together with nested MOC introduced in iOS 5 serve as an simpler and cleaner concurrency model, so I'm happily using it now. But there are several things that are not clear to me:
Do child MOCs pulls parent MOC's changes automatically? Or do I still need to coordinate them manually? If the latter, how to manually refresh the children to incorporate parent changes?
(I know how things work the other way around -- parent MOC gets changes from a child MOC when the child saves.)
What will happen on saving a child MOC if there are conflicts between changes in the child and changes in parent? Is mergePolicy also responsible for resolving conflicts between child MOC and parent MOC?
Yes and no, depending on your definition of "pulls parentMOCs changes automatically." If you mean, the next time a child fetches, it will get the updated data, then yes. If you mean will objects automatically get changed, then no. The reason is that a MOC is a scratchpad, and it will not change without you changing it.
Yes, you will have to resolve merge conflicts if you are making changes to the same objects/relationships from multiple places. This is a complex issue, and can not be sufficiently answered in a SO answer. You should really read the Core Data Programming Guide, especially the section on Change Management.
EDIT
Say I'm holding object A in child MOC and add a relationship B to A in parent MOC. What do I get if I access A.B? nil? What should I do if
I want to see A.B in child MOC? Use refreshObject:mergeChanges: to
refresh A, or use objectWithID: to fetch A again? Is there any way to
refresh the whole child MOC? I have a very complex object network in
child MOC and I don't want to(or just can't) refresh/re-fetch every
object one by one.
When looking at object A through the child MOC, you will NOT see the change in the parent MOC until you refetch from the child MOC. If you want to see what's in the parent, you can do so via another fetch on the MOC, or through any of the methods to get a apecific object from the store. However, I must stress that you need to read the documentation about each fetch, because they may have side effects. The exact answer depends on which approach you choose.
Typically, if you are doing continual change, you want to either make changes in a child, and push them to the parent, or handle the DidChange notification to make the changes seem "automatic."
Does mergePolicy work here?
Yes.
Again, the best answer I can give you here on SO is to point you to the "Core Data Programming Guide." Pay special attention to these two sections:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html#//apple_ref/doc/uid/TP40002484-SW1
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdChangeManagement.html#//apple_ref/doc/uid/TP30001201-CJBDBHCB