in your book “Concurrency by Tutorials” in “Chapter 11: Core Data” you are writing on page 98: “Core Data will generate a new context on a private queue for you to work with so that you don’t have to worry about any concurrency issues. Be sure not to use any other context in the closure or you’ll run into tough to debug runtime issues.”
I’m not quite sure what the core data stack is that they’re using, but based on what you’ve shown, it doesn’t seem to make sense to use. You’ve already go the context and you’ve saved it directly. Is this a nested/child context you’re using, or did you just do something like context = this.coreDataStack.context?
If it’s the latter, then don’t call into the coreDataStack again as
It’s a redundant lookup
You’re creating a retain cycle.
You’ve accessed self in your perform block, and if your controller goes away before that perform block finishes, you can have issues. Based on the error you show, I’m guessing that’s what happened…self no longer exists, and you’re then trying to access it.
@mmorey Can you chime in here? I see you wrote the tutorial being referenced.
Hi gargoyle, thanks for the quick reply! The code snippet I posted was directly taken from the tutorial, it’s not my own code: In the final project it’s in JournalListViewController method didFinish from Line 300. The variable context on which perform is called is a child context and self.coreDataStack.saveContext() saves the main context.
But now I noticed a little difference between my own and the tutorials implementation: my child context uses .privateQueueConcurrencyType whereas in the tutorial both contexts use .mainQueueConcurrencyType. Maybe this is my problem?
You’re right, that’s why it looks a little different in my implementation:
// save child context with .privateQueueConcurrencyType
saveContext(context) { [weak self] (success) in
guard let strongSelf = self else { return }
if success {
// Lets save the parent if existing
guard let parentContext = context.parent else {
completion?(true)
return
}
// save main context with .mainQueueConcurrencyType
strongSelf.saveContext(parentContext, completion: completion)
} else {
completion?(false)
}
}
This whole code block is inside the context.perform block of the child context (var context)
No, the code I posted in my previous comment is wrapped inside a perform block of the child context. That’s the only perform call in this code. It’s a lot more code separated into different methods, but if I break it down it’s basically this:
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = mainContext
context.perform { [weak self]
guard let strongSelf = self else { return }
// save child context with .privateQueueConcurrencyType
strongSelf.saveContext(context) { [weak self] (success) in
guard let strongSelf = self else { return }
if success {
// Lets save the parent if existing
guard let parentContext = context.parent else {
completion?(true)
return
}
// save main context with .mainQueueConcurrencyType
strongSelf.saveContext(parentContext, completion: completion)
} else {
completion?(false)
}
}
}
You’re gonna need to post this in the Core Data are I think as it’s really not a concurrency issue, and they’ll probably need to see your saveContext method. However, I can say, if saveContext isn’t doing a perform against the context you’re trying to save you’ll fail. All core data calls have to be performed in the right Core Data context.
But from the top view this would mean calling a mainContext.perform block inside a childContext.perform block. Is this the recommended way of saving both child and main context? Or should I it look like this maybe:
childContext.perform {
// Do some stuff like insert entities
childContext.save()
}
mainContext.perform {
mainContext.save()
}
For an child context that will be edited, then saved to the parent, it makes more sense to have both use the same concurrency type. The separate private type for a child is usually used for a long running fetch or process.
Perform is an async call. If you tried what you show in the last block, the second perform would get started right after the first one, which would not give the expected result. If you nest perform calls, as you did earlier, it will work as long as the two types are different, but could deadlock if both contexts were the same concurrency type.
You could try PerformAndWait, but that defeats the purpose of using a separate concurrency type, you might as well just do both on the main queue.
If the main context is parented by the store coordinator, it is common to only save it when the app goes to background or is exiting. Loss of data doing that is very rare. So you could just save the child context, and carry on. (That is less desirable if you are also synching with iCloud, though).
Thanks a lot! That answered all my questions I had about this issue so far. I think we gonna use a solution based on 4. As we don’t have any data sync with iCloud until now.