Once again it’s been quite some time (five weeks) since my last blog post. Getting my head stuck into this app is really making the time fly by unnoticed.
Early in September, I noticed that I was really starting to get bogged down in the code. The trouble with “learning to code” whilst creating an app is that you end up with an awful lot of “trial” code – code that you tried out, eventually found another solution, but the spaghetti mess is left behind either unused or used in a highly inefficient way.
And so I took the executive decision to start “version 3” of the app from scratch. I can reuse some of the “version 2” elements, such as views and extensions, but otherwise this would be a clean slate so that I don’t get lost in old code.
That was three weeks ago, during which time I’ve been really trying to get my head around Core Data and concurrency. What I thought I’d learned before was really only a tiny bit of what I needed to know. What I know now is a tiny bit more than that.
Xcode continually threw up it’s usual array of indecipherable error messages, and Googling for answers invariably resulted in 7-year old solutions using UIKit and a whole host of seemingly-gibberish code. I know one day I’ll need to learn UIKit, but a simple problem shouldn’t require such a confusing solution.
One of the many issues that have been troubling my feeble brains recently was an error along the lines of:
Thread 9: *** Collection <…> was mutated while being enumerated.
Oh, sure, I know the answer now but it took many hours over several days to wade through what was really going on.
The message implies that something you’re relying on (enumerating) is changing (mutating) and so Swift doesn’t know how to deal with it.
This largely comes from importing remote JSON
data into CoreData, which is happening while the app’s View is reading that data.
The solution comes down to threads. The View (UI) is always on the “main” thread, while other stuff isn’t necessarily so, and it’s a bit of a chore to figure out what is where.
I didn’t think too much was on the background thread, and I’d been using DispatchQueue.main.async
when updating @Published
variables as we learned early on in my CWC+ lessons. Well, it turns out I hadn’t learned well enough. I was doing what I’d been told to do, but not understanding why I was doing it.
It turns out that when using dataTask
, such as this:
let dataTask = URLSession.shared.DataTask (with: ...) { ... }
… the enclosed code is now on a background thread. Even when you call functions and whatever. So, when I was calling a function to populate Core Data, it was all happening on the background thread. This is, of course, a good thing. You don’t want tasks that take a “non-trivial” amount of time (a new phrase I discovered this week) to be holding up the UI.
The consequence of this, however, was that Core Data was being updated from the background thread whilst the View was reading it on the main thread. This led to unpredicatable behaviour and the aforementioned crash at unpredictable times.
With my limited knowledge, and even less understanding at the time, I did contemplate putting everything inside DispatchQueue.main.async
, but that didn’t seem to be a practical solution and I’m sure that’s not what it was designed for.
The proper course of action was to learn a bit more about “threads”.
To cut a long story slightly shorter, the problem was down to using a single viewContext
with Core Data when, really, we need a different viewContext
on the background thread from the one on the main thread.
This solution wasn’t immediately intuitive to me because, a few weeks earlier, I’d been getting Xcode errors related to multiple viewContext
calls, even though I only had one viewContext
. It took a while to solve that one, but it left that part of my head permanently traumatised and with a block against the very idea of using multiple viewContext
s.
I needed to bang some more knowlege into my head and, this time, try and understand what is going on.
Until now, I’d been using a single viewContext
, declared in my PersistenceController {}
struct thus:
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
private init() {
container = NSPersistentContainer(name: "CoreDataModel")
container.loadPersistentStores { _, error in
if let error = error {
print("Unresolved error \(error)")
}
}
}
}
And then, using the following, I could just throw the variable ‘moc
‘ around all over the place that I needed it:
let moc = PersistenceController.shared.container.viewContext
That works, until it doesn’t and you run into the “threads issue”.
After lots of hunting around for why Xcode was complaining with its infinite variety of arcane mystical errors, I discovered that the solution is relatively simple. Isn’t it strange that the gap between “minor problem” and “simple solution” is a language barrier the likes of which might be encountered by a Martian coming to Earth and asking to be taught Venusian?
The solution revealed itself to be the addition of this simple line into my PersistenceController
struct:
let backgroundContext: NSManagedObjectContext
And then add the following to the init()
:
backgroundContext = container.newBackgroundContext()
Now, all I had to do was to go through my code, find anything that was using ‘moc
‘ (primarily in my DataModel
class), and add the following:
let mocBkg = PersistenceController.shared.backgroundContext
After that, it’s just a matter of checking out whatever was on a background thread (primarily by checking out anything between a DataTask
parentheses) and hand around ‘mocBkg
‘ instead of ‘moc
‘.
It’s now a few hours later of testing (and coffee – I’d like to say “or “moc-a” for laughter value, but I can’t afford anything but instant coffee these days) and that d*mn ‘enumerated’ crash error hasn’t raised its ugly head since.
Now that’s what I call progress!
It’s been a tough old week, but I feel I understand a whole lot more about how my app does what it does. Every piece of understanding is another piece of assurance that I won’t have trouble with this brick wall again in the future.
(Don’t worry – my next blog post will be briefer!)