Core data context save: which thread/queue?

Hi

I'm getting into core data (slowly) and am a little confused as to how I should be saving the data.

I've looked at various examples and they all use persitantContainer.viewContext, and in several cases that is defined in the appDelegate.swift file.

The developer documentation states that viewContext is "The managed object context associated with the main queue (read-only)". Yet elsewhere the recommendation is that data saves should be on a background thread/queue to avoid interrupting the UI.

I'm pretty sure I've missed something here... Am I perhaps just confusing threads and queues perhaps?


Cheers

Replies

CoreData objects exist in contexts, and can have different values in different contexts. And the changes from one context can be propagated to another context. The propagation mechanism ends up being thread safe because the change message ends up being a buffer between the two.


So what generally happens is that you have Context A set up as a background context that saves directly to the persistent store; and then set up Context B for the primary thread/queue, and rig up B so that it's changes get propagated to A. That way B doesn't have to perform the potentially lengthy 'save to disk' operation and instead just performs the quicker 'produce change message' operation.


That's how you end up with changes being made in one thread and saved to the persistent store in another.

Thanks... but I’m still confused.

Originally I used the persistent container as generated by Xcode, i.e. In the app delegate. Then I read that I should be saving on a background thread/queue (I forget exactly) so I tried executing the save inside a DispatchQueue.global(Qos: .background).async. This caused an error (re it being in the app delegate), so I moved the persistent container code out of the app delegate and into a class of its own. This sort of worked but occasionally I got an error stating something along the lines that a recursive call to -save; context aborted. Note that I was still using DispatchQueue. Given I was saving blocks of location data, and the error seemed to happen when the location was changing very quickly, I figured perhaps the saves were tripping over each other so I removed the DispatchQueue. This seems to have fixed the issue (I also increased the locations count threshold that triggered the save from 10 to 50 to decrease the number of saves.

GIven that 50 or so location records is still a tiny amount of data this doesn’t seem to have affected responsiveness of the UI.

My concern is that the problem may have gone away but I still don’t fully understand how I should be handling core data saves etc. correctly, particularly in light of the documentation stating viewcontext is the context associated with the main queue. I think I’ve simply made the problem go away by reducing the frequency of saves. Am I still saving on the main queue perhaps?

Apologies for being slow and thanks again for the response. If you have any example code or link it’d be much appreciated.

Now I’m 100% sure it’s wrong, I’ve just tested the code on an iPad instead of an iPhone, and now the app just crashes out completely when I trigger the save. I put in an exception break point and the code stops on the “class AppDelegate:” line 😟, it’s working perfectly on an iPhone 6.

Well I added some breakpoints and this is whats happening:

1) process is recording locations.

2) I tap stop, this SHOULD pop up an alert controller prompting to save or cancel.

3) the process aborts after the line that presents the alert, without actually presenting.

4) execution stops at the app delegates class header line.

5) on thread 1, CFRunLoopRunSpecific there is a line stating “The_process_has_forked_and_you_cannot_use_this_core_foundation_functionality_you_must_exec”

seems like I’m going backwards. I’ve no idea what to try next, just flshed though some Apple documentation and it went straight over my head.

I seem to be developing a flattened forehead

You're hitting some "I can't see your code, so I can't see what horrible misconception you implemented" limits. :-/


1. Because of how the runtime works, the first few dozen stack frames of the main thread are just internal methods with confusing names.

2. If something goes wrong, the stuff in the stack above where the debugger stopped often ends up being more confusing internal names. It's like crashing a car--where it stops isn't always related to what blew out. 😮


As as far as the CoreData stuff goes, there have been a few realignments and paradigm changes in "How are you supposed to structure Core Data stacks?" over the years.

  • First came manual threads and thread containment (in the iOS 3 era)
  • Later came NSManagedDocument and "merge changes and save in the background"
  • Now we've got NSPersistentContainer and its independent parallel contexts.

If you read advice on the internet, you have to know which "era" (not the right word, but...) it's from and whether you're working on code from the same era. The code structures used are different in each of the eras.


It's also worth knowing that the thread/dispatch queue system has a lot of optimization in it to avoid switching if possible. Short version is that sometimes stuff works in spite of it being wrong because the execution got squeezed together in a fortunate ordering. But on a different device (or under different conditions) execution happens in a different order and things **** up.

Thanks again... I think it’s time to give it a rest for today and have another go in the morning (possibly even start from scratch). I hate to say it but I actually caught myself longing for a good old VB editor or even SSMS, definitely time for some sleep. I’ve already learned to limit search results to the last year, I just keep forgetting ;o)

I just moved a block of code that draws a poly line on a map based on an existing saved track. Thought I’d run it on a background thread as it was slowing the view load, now it’s telling me the array mutated while being iterated (there’s a lot of data points).

I’ll aim to post some code tomorrow/later today. I guess for this last error I need to somehow not start drawing the poly line until the array is fully populated. It’s sourced from a managed objects relationship.

Cheers