Recently, I noticed many crashes reported daily for context.save() in my code. I tried to resolve the issue by refactoring some of the code. My key change is to use context.performAndWait to fetch Core Data objects instead of passing them around.
I refactored the code to pass NSManagedObjectID around instead of NSManagedObject itself because NSManagedObjectID is thread-safe.
However, even after the refactor, the issue is still. The crash log is attached as below.
I'm confused that it doesn't crash on all devices so I have no clue to find the pattern of the crashes. I also enabled -com.apple.CoreData.ConcurrencyDebug 1 on my local dev box, but seems the error cannot be caught.
The new code (after I refactored) still causes the crash:
private func update(movements: [InferredMovement], from visitOutObjectId: NSManagedObjectID, to visitInObjectId: NSManagedObjectID?) {
let context = PersistenceController.shared.container.viewContext
context.performAndWait {
guard let visitOut = try? context.existingObject(with: visitOutObjectId) as? Visit else {
return
}
var visitIn: Visit?
if let visitInObjectId {
visitIn = try? context.existingObject(with: visitInObjectId) as? Visit
}
visitOut.movementsOut.forEach { context.delete($0) }
visitIn?.movementsIn.forEach { context.delete($0) }
for inferred in movements {
let movement = Movement(context: context)
movement.type = inferred.type
movement.interval = inferred.interval
movement.visitFrom_ = visitOut
movement.visitTo_ = visitIn
}
visitOut.objectWillChange.send()
context.saveIfNeeded()
}
}
Note, saveIfNeeded() is an extension function implemented as:
extension NSManagedObjectContext {
/// Only performs a save if there are changes to commit.
@discardableResult
public func saveIfNeeded() -> Error? {
guard hasChanges else {
return nil
}
do {
try save()
return nil
} catch {
defaultLogger.error("Core Data Error: Failed to save context")
return error
}
}
}
Crash context