In my iOS application, I am attempting to sync core data with a web back end. I want to use a separate background managed object context for the sync so I can keep my main context free to accept changes from the ui while the sync is processing. Both contexts are children of my write-to-disk context as per this blog post http://www.cocoanetics.com/2012/07/multi-context-coredata/.
My question is, how can I merge both children contexts before I save to disk?
If I subscribe to contextDidSaveNotifications, I can merge the contexts using
[mainContext mergeChangesFromContextDidSaveNotification:syncFinishedNotification];
but according to the documentation...
"This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted."
I don't want to refresh the updated objects and lose changes made to the mainContext, but rather merge both change sets.
I am new to multi-context core data, so I may be thinking of this in the wrong way.
Any ideas?
Merging changes in Core Data is always a process of taking changes in one managed object context and then applying them to another context. If both contexts might acquire new changes at the same time, the merge is affected by the context's merge policy. If there are no conflicting changes, there's nothing to worry about. If there might be, though, you'll need to choose an appropriate merge policy.
The default value if you do nothing is NSErrorMergePolicyType, which means that saving changes would fail after merging changes. You almost certainly don't want that. But there are other predefined policies to choose from. NSMergeByPropertyObjectTrumpMergePolicyType is often a good choice here, because gives priority to unsaved conflicting changes. So if the sync context makes conflicting changes to an object the user is editing, the user's changes are preserved. There are a few other canned options. If none of them fit, you can always subclass NSMergePolicy and do whatever you like. That's rarely necessary, though.
Related
I'm having a doubt about coredata: Is it possible to do fetch and save in Private context,Without notifying anything to main context.?I saw in many example and tutorials that In every save or fetch finally we are end up with main context. Why it is necessary? If i call save on private queue with same persistent coordinator it won't write to Disc? Please clear my doubts before you down vote.
Different managed object contexts are not automatically aware of changes made by each other. If you fetch the same object in two contexts, change it in one, and save changes, the other context will not know about the new values unless you merge the changes somehow. That means it has old, stale data. This is why nearly everyone merges changes, because you want the different contexts to be aware of new changes.
In your case, if you make changes on a private context but do not do anything to merge those changes into a main-queue context, the main queue context will not know about the changes and will still show old values.
The simple answer is yes, you can do fetch and save on a private context working against the same persistent store coordinator (and persistent store) as the main context. I have it implemented for me. Usually, you want the main context to be aware of the changes by doing a merge of the changes, but that is a not a requirement of coredata. It is up to you based on what what data you are trying to keep in each context.
I'm in the middle of development of an iOS application after working quite some time with Core Data and Magical Record and having an error:
error: NULL _cd_rawData but the object is not being turned into a fault
I didn't know Core Data before this project and as it turns out I was very naive to think I can just work with Magical Record without worrying about concurrency, as I haven't dedicated any thoughts/work about managed contexts for the main thread and background threads.
After A LOT of reading about Core Data Managed Object Contexts and Magical Record, I understand that:
NSManagedObjects are not thread safe.
NSManagedObjectId IS thread safe.
I can use: Entity *localEntity = [entity MR_inContext:localContext] of Magical Record to work with an entity in a background thread's context.
I should use Magical Record's saveWithBlock:completion: and saveWithBlockAndWait: methods to get a managed context to use for background threads.
A little information regarding my application:
I'm using the latest version of Magical Record which is 2.2.
I have a backend server which my application talks to a lot.
Their communication is similar to Whatsapp, as it uses background threads for communicating with the server and updating managed objects upon successful responses.
I'm wrapping the model with DataModel objects that hold the managed objects in arrays for quick referencing of UI/background use.
Now - my questions are:
Should I fetch only from the UI thread? Is it okay that I'm holding the managed objects in DataModel objects?
How can I create a new entity from a background thread and use the newly created entity in the DataModel objects?
Is there a best design scenario that I should use? specifically when sending a request to the server and getting a response, should I create a new managed context and use it throughout the thread's activity?
Let me know if everything is clear. If not, I'll try and add clarity.
Any help or guidelines would be appreciated.
Thanks!
I'm not working with MagicalRecord, but these questions are more related to CoreData than to MagicalRecord, so I'll try to answer them :).
1) Fetching from main(UI) thread
There are many ways how to design app model, so two important things I've learned using CoreData for few years:
when dealing with UI, always fetch objects on main thread. As You correctly stated NSManagedObjects are not thread safe so you can't (well, can, but shouldn't) access their data from different thread. NSFetchedResultsController is Your best friend when you need to display long lists (eg. for messages – but watch out for request's batchSize).
you should design your storage and fetches to be fast and responsive. Use indexes, fetch only needed properties, prefetch relationships etc.
on the other hand if you need to fetch from large amount of data, you can use context on different thread and transfer NSManagedObjectIDs only. Let's say your user has huge amount of messages and you want to show him latest 10 from specific contact. You can create background context (private concurrency), fetch these 10 message IDs (NSManagedObjectIDResultType), store them in array (or any other suitable format for you), return them to your main thread and fetch those IDs only. Note that this approach speed things up if fetch takes long because of predicate/sortDescriptor not if the "problem" is in the process of turning faults into objects (eg. large UIImage stored in transformable attribute :) )
2) Creating entity in background
You can create the object in background context, store it's NSManagedObjectID after you save context (object has only temporary ID before save) and send it back to your main thread, where you can perform fetch by ID and get the object in your main context.
3) Working with background context
I don't know if it's exactly the best, but I'm pretty much satisfied with NSManagedObjectContext observation and merging from notifications. Checkout:
mergeChangesFromContextDidSaveNotification:
So, you create background context, add main context as observer for changes (NSManagedObjectContextObjectsDidChangeNotification) and background context automatically sends you notifications (every time you perform save) about all of it's changes – inserted/updated/deleted objects (no worries, you can just merge it by calling mergeChangesFromContextDidSaveNotification:). This has many advantages, such as:
everything is updated automatically (every object you have fetched in "observing context" gets updated/deleted)
every merge runs in memory (no fetches, no persisting on main thread)
if you implement NSFetchedResultsController's delegate method, everything updates automatically (not exactly everything – see below)
On the other side:
take care about merging policy (NSMangedObjectContext mergePolicy)
watchout for referencing managed objects that got deleted from background (or just another context)
NSFetchedResultsController updates only on changes to "direct" attributes (checkout this SO question)
Well, I hope it answers your questions. If everything comes up, don't hesitate to ask :)
Side note about child contexts
Also take a peek to child contexts. They can be powerful too. Basically every child context sends it's changes to parent context on save (in case of "base" context (no parent context), it sends it's changes to persistent coordinator).
For example when you are creating edit/add controller you can create child context from your main context and perform all changes in it. When user decides to cancel the operation, you just destroy (remove reference) the child context and no changes will be stored. If user decides to accept changes he/she made, save the child context and destroy it. By saving child context all changes are propagated to it's parent store (in this example your main context). Just be sure to also save the parent context (at some point) to persist these changes (save: method doesn't do the bubbling). Checkout documentation of managing parent store.
Happy coding!
I am using Magical Record library to easily maintain my core data related project.
Here i have a situation where i have to remove all changes done to default context and prevent saving it into the database.
The problem is i am not using any method of Magical Record which performs save operation. So it is not saving into the database fine. But it maintains data in current context.
How do i clear all the changes made to current context or root context?]
Thanks,
Pratik
Don't use the default context for changes you are not sure are going to eventually be persisted. The easiest way to do this is to create a new context. With MagicalRecord, creating a new context will automatically merge your changes to the default context when you save it. If you don't want to keep the changes in your new context, then just release it, along with any objects that use that contexts and those changes will be discarded. You don't have to go and manually undo everything. When you take advantage of multiple contexts, you will have a lot less work to do.
I've recently noticed this strange thing about undo mechanism in Core Data and it's bothering me ever since.
A quote from NSManagedObjectContext documentation for -undo method:
Sends an undo message to the receiver’s undo manager, asking it to reverse the latest uncommitted changes applied to objects in the object graph.
To reverse the latest uncommitted changes, sounds simple, right?
However, it's not what is actually happening! Even if I save the context with changes on my managed object, the following -undo call will still successfully reverse the changes. Isn't it against the thing stated in the docs?
Perhaps I'm doing something wrong? I can post my little testing code if needed. I'm really confused.
You should be confused. The Core Data documentation is a hot mess. They use a lot of words like "uncommitted" in arguably inappropriate ways. They seem to mean objects whose properties isFaulted is equal to NO when they say "uncommitted".
The Core Data Programming guide goes into more detail:
Change and Undo Management
A context keeps strong references to managed objects that have pending
changes (insertions, deletions, or updates) until the context is sent
a save:, reset , rollback, or dealloc message, or the appropriate
number of undos to undo the change.
The undo manager associated with a context keeps strong references to
any changed managed objects. By default, in OS X the context’s undo
manager keeps an unlimited undo/redo stack. To limit your
application's memory footprint, you should make sure that you scrub
(using removeAllActions) the context’s undo stack as and when
appropriate. Unless you keep a strong reference to a context’s undo
manager, it is deallocated with its context.
The wording/vocabulary in the documentation is not clear or consistent. I believe the intended usage is that the you should call removeAllActions on the context's undoManager property when appropriate for your application to avoid unlimited memory growth.
I'm syncing with a MySQL database.
Initially, I was going to loop through all my new/modified objects and set all the foreign keys for that object and then do the next object, and so on... But that's a lot of fetch requests.
So instead I wanted to loop through all my new/modified objects and set the foreign keys one at a time. So the first pass over my objects sets fk1, my next sets fk2, so on...
Cool, fetch requests drastically reduced. Now I'm curious if I could thread these fk setters. They aren't dependent on each other, but they are modifying the same object, even though they're only setting one relationship, and it's a different relationship. Speaking in git terms, these changes could be 'merged' together without any conflict, but is it possible to push changes in one child managedObjectContext(childContext:save) up to the parentManagedObjectContext(parent:performBlock^{parent:save}) and pull it down in another, different child managedObjectContext(???)? Or will the merge policy only take one childContext's version of the object and leave the other fks effectively unchanged.
I know this exists: NSManagedObjectContext/refreshObject:mergeChanges:
But that's on an object by object level. Will that cause a bunch of fetches? Or will that update my whole context at once/in batches?
Following Apple's suggestion from here:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
I've created/updated my values before I start setting any relationships, so all entities already exist before I try to point any relationships at them.
Aside: We have a couple apps that could benefit from the concurrency, because they throw a considerable amount of data around, and with the quad core iPad apps, this would really help out with the time the initial sync takes.
I'n not sure what you are trying to do and why (you could write less lines in your question and be more clear), but here are some guidelines for working with Core Data:
-- NSManagedObjectContext is not thread safe. Therefore, you need to limit your access to this managed object context to happen inside 1 thread. Otherwise, you may end up having many errors you can't understand.
-- NSManagedObjectContexts apart from doing other things, serve like "snapshots" of your persistent store. That means that when you change an object, you save it to the persistent store, you post a NSManagedObjectContextDidSaveNotification and then call mergeChangesFromContextDidSaveNotification: in another place inside your program in order to load the latest data from the persistent store. Be careful of thread safety.
NSManagedObjectContext/refreshObject:mergeChanges: according to apple is not about just refreshing a managed object. If you pass YES as the second argument, it will write any pending changes from this managed object context to the persistent store, and will load any other changes to other properties of this object from the persistent store, thus "synchronizing" your object with the persistent store. If you pass NO as the second argument, the object loses any pending changes, and it is being turned to a fault. That means that when you attempt to access it, the Managed Object Context will reload the object as it was last saved to the database. It will NOT reload the entire managed object context. It will only operate on the object.
Aside: I have written a blog post that scratches the surface of asynchronous loading from a core data database. In my case, since I'm doing heavy lifting with the database, I ended up using an NSOperation that operates with its own NSManagedObjectContext, and using serial GCD queues to save large chunks of data, since it was faster than having multiple threads accessing the same persistent store, even if they operate on different managed object contexts.
I hope I helped.