Core Data Background Context Best Practice

Core Data background context best practice

This is an extremely confusing topic for people approaching Core Data for the first time. I don't say this lightly, but with experience, I am confident in saying the Apple documentation is somewhat misleading on this matter (it is in fact consistent if you read it very carefully, but they don't adequately illustrate why merging data remains in many instances a better solution than relying on parent/child contexts and simply saving from a child to the parent).

The documentation gives the strong impression parent/child contexts are the new preferred way to do background processing. However Apple neglect to highlight some strong caveats. Firstly, be aware that everything you fetch into your child context is first pulled through it's parent. Therefore it is best to limit any child of the main context running on the main thread to processing (editing) data that has already been presented in the UI on the main thread. If you use it for general synchronisation tasks it is likely you will be wanting to process data which extends far beyond the bounds of what you are currently displaying in the UI. Even if you use NSPrivateQueueConcurrencyType, for the child edit context, you will potentially be dragging a large amount of data through the main context and that can lead to bad performance and blocking. Now it is best not to make the main context a child of the context you use for synchronisation, because it won't be notified of synchronisation updates unless you are going to do that manually, plus you will be executing potentially long running tasks on a context you might need to be responsive to saves initiated as a cascade from the edit context that is a child of your main context, through the main contact and down to the data store. You will have to either manually merge the data and also possibly track what needs to be invalidated in the main context and re-sync. Not the easiest pattern.

What the Apple documentation does not make clear is that you are most likely to need a hybrid of the techniques described on the pages describing the "old" thread confinement way of doing things, and the new Parent-Child contexts way of doing things.

Your best bet is probably (and I'm giving a generic solution here, the best solution may be dependent on your detailed requirements), to have a NSPrivateQueueConcurrencyType save context as the topmost parent, which saves directly to the datastore. [Edit: you won't be doing very much directly on this context], then give that save context at least two direct children. One your NSMainQueueConcurrencyType main context you use for the UI [Edit: it's best to be disciplined and avoid ever doing any editing of the data on this context], the other a NSPrivateQueueConcurrencyType, you use to do user edits of the data and also (in option A in the attached diagram) your synchronisation tasks.

Then you make the main context the target of the NSManagedObjectContextDidSave notification generated by the sync context, and send the notifications .userInfo dictionary to the main context's mergeChangesFromContextDidSaveNotification:.

The next question to consider is where you put the user edit context (the context where edits made by the user get reflected back into the interface). If the user's actions are always confined to edits on small amounts of presented data, then making this a child of the main context again using the NSPrivateQueueConcurrencyType is your best bet and easiest to manage (save will then save edits directly into the main context and if you have an NSFetchedResultsController, the appropriate delegate method will be called automatically so your UI can process the updates controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:) (again this is option A).

If on the other hand user actions might result in large amounts of data being processed, you might want to consider making it another peer of the main context and the sync context, such that the save context has three direct children. main, sync (private queue type) and edit (private queue type). I've shown this arrangement as option B on the diagram.

Similarly to the sync context you will need to [Edit: configure the main context to receive notifications] when data is saved (or if you need more granularity, when data is updated) and take action to merge the data in (typically using mergeChangesFromContextDidSaveNotification:). Note that with this arrangement, there is no need for the main context to ever call the save: method.
Sample Image

To understand parent/child relationships, take Option A: The parent child approach simply means if the edit context fetches NSManagedObjects, they will be "copied into" (registered with) first the save context, then the main context, then finally edit context. You will be able to make changes to them, then when you call save: on the edit context, the changes will saved just to the main context. You would have to call save: on the main context and then call save: on the save context before they will be written out to disk.

When you save from a child, up to a parent, the various NSManagedObject change and save notifications are fired. So for example if you are using a fetch results controller to manage your data for your UI, then it's delegate methods will be called so you can update the UI as appropriate.

Some consequences: If you fetch object and NSManagedObject A on the edit context, then modify it, and save, so the modifications are returned to the main context. You now have the modified object registered against both the main and the edit context. It would be bad style to do so, but you could now modify the object again on the main context and it will now be different from the object as it is stored in the edit context. If you then try to make further modifications to the object as stored in the edit context, your modifications will be out of sync with the object on the main context, and any attempt to save the edit context will raise an error.

For this reason, with an arrangement like option A, it is a good pattern to try to fetch objects, modify them, save them and reset the edit context (e.g. [editContext reset] with any single iteration of the run-loop (or within any given block passed to [editContext performBlock:]). It is also best to be disciplined and avoid ever doing any edits on the main context.
Also, to re-iterate, since all processing on main is the main thread, if you fetch lots of objects to the edit context, the main context will be doing it's fetch processing on the main thread as those objects are being copied down iteratively from parent to child contexts. If there is a lot of data being processed, this can cause unresponsiveness in the UI. So if, for example you have a large store of managed objects, and you have a UI option that would result in them all being edited. It would be a bad idea in this case to configure your App like option A. In such a case option B is a better bet.

If you aren't processing thousands of objects, then option A may be entirely sufficient.

BTW don't worry too much over which option you select. It might be a good idea to start with A and if you need to change to B. It's easier than you might think to make such a change and usually has fewer consequences than you might expect.

iOS Core Data: is enough saving only a background context? why or why not?

I would like to know if just saving a background context like in the below example is enough or I should also save the main context and why.

A Core Data managed object context is a workspace; changes that you make to the data exist only in that context until you save, at which point the changed objects are written back to the data store. If you made changes in the "background" context that you want to persist, you need to save that context. If you made changes in the "main" context that you want to persist, you need to save that context.

If there's a parent/child relationship between the two contexts, if you want to permanently save changes in the child context you'll need to first save in the child context and then save in the parent context. For example, if your "main" context is the parent store for your "background" context, saving in the background context will push the changes up to the main context, and you'll then need to save in the main context. (And if the main context is a child of some other context, you'll need to save all the way up the chain until you reach the root context.)

Core Data Best Practices - Am I utilizing C.D. Correctly and Efficiently? - iOS Swift

You should really only need to create one core data stack throughout the lifetime of the app. (There are exceptions to this but for most apps).

I am currently using JSQCoreDataKit to manage the creation of the stack and saving contexts. It would definitely be worth taking a look at this.

The normal approach to core data is something like...

  1. Create the core data stack on launch of the app. Normally accessed through a singleton (NOT IN THE APP DELEGATE).

  2. For reading data from core data get the mainContext from the core data stack and perform fetches on this mainContext.

  3. For writing (adding, updating, deleting) data back you can use the mainContext but can also get a backgroundContext or childContext from the core data stack. The perform the updates and saveContext inside a perform block on the context. (This will merge changes to the main context for you to read).

This should cover most of what you want to do.

Have a look at JSQCoreDataKit. It makes the creation of the managed object context much much simpler.

Edit to clarify point 1

Putting things into the AppDelegate is a very clunky and lazy way to get at data across the board. The AppDelegate is a singleton so it seems like the perfect place to put it. But then you add more and more and suddenly you have a huge app delegate that drives your entire app.

Using the Single responsibility principle your app delegate should do one thing... be a delegate for you app. It should respond to app state changes etc...

I forgot to add... If you create a second target (say a TVOS target) for your app. It will not use the same AppDelegate. If all your CoreData code (and other code) is inside the AppDelegate then the TVOS app will not be able to access it. Putting it in another class that is accessible to both apps means that both apps can share the code you use for CoreData (et al).

It is very easy to create another file that holds your core data stack and initiate it whenever you first need access to core data. (Not necessarily from the AppDelegate but from the first place you need to make a read/write).

RE placing the core data stack setup in the initial view controller. You could do that. You then have the issue of how do you get to that core data stack from every other view controller in the app. You could either make the initial view controller a singleton (don't do this) or you could pass the stack around.

Both approaches can be done but CoreData is inherently a singleton. There is only one set of data on your phone's disc. So creating a singleton here is not a bad thing.

If you do make a singleton then make it purely a core data stack singleton. I have one called something like CoreDataStackManager. All it does is hold the coreDataStack property.

I insert my CoreData in background Context and when I fetch, it doesn't show the data

The mistake you are making is to not test the many little pieces of this code which could be not working as expected. To troubleshoot something like this, you must start at the beginning and test one piece at a time. The first issue I see is confusion between managed object contexts…

  • It appears that the method saveManagedObjectContext is a getter for your main managed object context. In English, save is a verb, so that this method name implies that it is saving some managed object context. If I am correct, you should change the name saveManagedObjectContext to maybe mainMangaedObjectContext.

  • The statement _managedObjectContext = [appDel managedObjectContext] is quite nonstandard, assigning to an instance variable from what appears to be its getter. If, as usual, _managedObjectContext is the instance variable backing the property managedObjectContext, this statement has no effect.

  • In your first method you use [appDel saveManagedObjectContext], and in your second method you use [appDel managedObjectContext]. It looks like these should be the same context in order for your fetch to work. Are they?

UPDATE:

As you stated in your comment, that fixes the saving, but now you have a performance problem – saving to the persistent store on disk blocks the user interface, which is what your original code was trying to fix.

This is a solved problem. The solution is that your main-thread context, which interfaces the user, should be a child of your background-thread context which interfaces to the persistent store. It is explained nicely with code in this 2015 blog post by Marcus Zarra. For further reading, Chad Wilken has published a slight variation. Both are written in Objective-C for you :)

How to save to managed object context in a background thread in Core Data

I have solved this after reading the helpful comments from @user1046037 (thanks!) and researching how to use context.perform { } and context.performAndWait { }. I will post my code below in case it benefits anyone else, as I couldn't find any examples on SO in swift:

initiateProgressIndicator() // start the spinner and 'please wait' message

let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = coreDataStack.managedContext

// Create a background task
childContext.perform {
// Perform tasks in a background queue
self.coreDataStack.importDefaultData(childContext: childContext) // set up the database for the new game

do {
// Saves the tasks done in the background to the child context
try childContext.save()

// Performs a task in the main queue and wait until this tasks finishes
self.coreDataStack.managedContext.performAndWait {
do {
// Saves the data from the child to the main context
try self.coreDataStack.managedContext.save()

self.activitySpinner.stopAnimating()

// Transition to the next screen
let vc: IntroViewController = self.storyboard?.instantiateViewController(withIdentifier: "IntroScreen") as! IntroViewController
vc.rootVCReference = self
vc.coreDataStack = self.coreDataStack
self.present(vc, animated:true, completion:nil)

} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}

In my CoreDataStack class:

func importDefaultData(childContext: NSManagedObjectContext) {
// import data into Core Data here, code not shown for brevity

saveChildContext(childContext: childContext)

// import more data into Core Data here, code not shown for brevity

saveChildContext(childContext: childContext)

// import final data here

saveChildContext(childContext: childContext)
}

func saveChildContext(childContext: NSManagedObjectContext) {
guard childContext.hasChanges else {
return
}

do {
try childContext.save()
} catch {
let nserror = error as NSError
}
}

If this approach is not best practise or anyone can see a way to improve it, I'd appreciate your thoughts. I should add that I found the following link very helpful: https://marcosantadev.com/coredata_crud_concurrency_swift_1



Related Topics



Leave a reply



Submit