Can you edit the same NSManagedObject in 2 different ManagedObjectContexts and merge their changes? - ios

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.

Related

Magical Record with concurrency

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!

Merging multiple child managed object contexts

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.

Keeping a NSManagedObject up to date while retaining it

I have a NSManagedObject instance that represents a user in my application. I am retaining this instance and passing it between view controllers for the interface to reference. The managed object context (MOC) the user instance belongs to is a main queue MOC that is a child of a private queue MOC that saves directly to the persistent store.
My core data persistent store is updated in the background on a separate background queue. These updates are saved to a private queue MOC that is then committed to the main private queue MOC and subsequently saved to the persistent store.
My question is, how can I be sure that the user NSManagedObject instance will stay up to date? I'm aware of the existence of refreshObject:mergeChanges:, however, it seems complex to set up a of NSManagedObjectContextDidSaveNotification observers to simply keep an object instance up to date. I can see this approach becoming unruly when trying to keep multiple NSManagedObject instances up to date.
From experience, your best option is don't try to keep it up to date. You need to use implement the NSManagedObjectContextDidSaveNotification to keep your context up to date -- you can't get around that -- but to get a valid object, you'll need to re-query it after every update. The easiest approach to that will be application-dependent, but I frequently use unique, server-generated ID's to pass objects around, then fetch them out of the database using those when I need to use them. (The unique ID's are necessary because I'm generally consuming an API that uses them, so your results will vary) The only place where that technique may or may not work is where you're generating data locally, and haven't (yet) uploaded it to the database where it get's it's permanent ID. I generally special-case those and have a device ID separate from the 'real' ID, just to keep track of them until they get their real ID. Anything that doesn't have a 'real' ID is something my logic is aware of as something that needs to be persisted to server, so that works for me.

Avoiding registered object buildup (memleak) in NSManagedObjectContext

I have a memory-intensive iOS app and I'm working on making sure that memory usage does not build up over time. My app has a "main" context that lives for the lifetime of the app, with other smaller contexts being spawned occasionally for background tasks.
One thing I have noticed is that NSManagedObjects appear to remain registered in the main context long-term and the only way to truly reclaim all the memory associated with pulling the objects from the DB is to call [NSManagedObjectContext reset].
This of course results in a nice drop in memory usage as all the registered objects from recently closed list views are properly ejected from memory, however it is annoying because you have just invalidated every object that was registered in that context that you still have a reference to (i.e. objects that are refered to by views that are still open), and you now need to re-fetch all the these objects from the database to avoid exceptions for accessing an invalidated object.
Is this the only way of flushing out the registered object set from an NSManagedObjectContext, or is there a better way that successfully ejects all the registered objects you no longer have references to, but doesn't invalidate all the NSManagedObjects that are still alive?
NSManagedObjectContext has an internal row cache, and the only way you can clear that out is by resetting the context. If you're actually experiencing memory issues, a few things that may help are:
Managed objects retain their related objects. If you have managed object A that's referenced by a relationship from some other managed object B, then object A will remain in memory even if you've released all references to it. It won't actually be deallocated until object B is deallocated or re-faulted, because it will still be retained by B.
One way of dealing with this (and other memory issues) is by calling refreshObject:mergeChanges: on the MOC for objects you aren't currently using, with the second argument set to NO. That re-faults the object, i.e. makes the object go back to the initial "fault" state, unloading its property values and relationships. With A and B from before, re-faulting B would release the relationship to A. Keep in mind this will lose any unsaved changes on B, so make sure you've saved first if necessary.
If your managed objects contain any kind of large binary data, try moving that data into a separate entity or out of Core Data altogether to avoid loading it into memory when it's not needed.
reset on the MOC is basically the nuclear option when it comes to Core Data memory management. It's extremely effective but as you've found can be very dangerous. It's best avoided unless you already aren't using any objects loaded from the MOC.

Best way to 'dump' a Core Data stack?

My app needs to be able to disconnect from a server and connect to another one at whim, which necessitates dumping whatever persistent store we have. Issue here is that releasing the 'main' managed object context means whatever objects in it that I have laying around fault, which causes all sorts of unexpected little issues and crashes.
Is there a better way to 'reset' my stack/managed objects littered around the program than just calling release on everything in my Core Data stack?
You need to close down you Core Data stack from the top down.
Make sure that no managed objects are retained by any object other than the managed object context e.g. make sure the objects aren't held in an array owned by a UI controller.
Save the managed object context to clean up any loose ends.
Fully release the context and nil it. The context should never be retained by more than one object e.g the app delegate, anyway.
Send removePersistentStore:error: to the persistent store coordinator.
Use standard file operations to delete the actual store file.
Changing Core Data like this on the fly is difficult because Core Data isn't just a little database hung off the side of the app. It is intended to serve as the apps entire model layer. Since Apple is really into Model-View-Controller design, then the model is the actual core of the program (hence Core Data's name.) As such, you can't really turn it on and off the way you would a mere SQL database.
You might actually want to rethink your design so that you can change servers without having to shut down your entire data model. E.g. simply delete all managed objects associated with an unused server.
If you mean you want to fault all objects so they will be fetched again from your persistent store,
[managedObjectContext reset];

Resources