I have CoreDataStack
I've added debug options for CoreData debugging "-com.apple.CoreData.ConcurrencyDebug 1"
class CoreDataStack {
public enum SaveStatus {
case saved, rolledBack, hasNoChanges, error
}
private var modelName: String
var viewContext: NSManagedObjectContext
var privateContext: NSManagedObjectContext
var persisterContainer: NSPersistentContainer
init(_ modelName: String) {
self.modelName = modelName
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores { persisterStoreDescription, error in
print("CoreData", "Initiated \(persisterStoreDescription)")
guard error == nil else {
print("CoreData", "Unresolved error \(error!)")
return
}
}
self.persisterContainer = container
self.viewContext = container.viewContext
self.viewContext.automaticallyMergesChangesFromParent = true
self.privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
self.privateContext.persistentStoreCoordinator = container.persistentStoreCoordinator
self.privateContext.automaticallyMergesChangesFromParent = true
}
func createTemporaryViewContext() -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = self.privateContext
context.automaticallyMergesChangesFromParent = true
return context
}
func saveTempViewContext(tempContext context: NSManagedObjectContext, completion: ((CoreDataStack.SaveStatus) -> Void)? = nil) {
guard context.hasChanges || privateContext.hasChanges else {
completion?(.hasNoChanges)
return
}
context.performAndWait {
do {
try context.save()
}
catch {
completion?(.error)
return
}
}
privateContext.perform { [weak self] in
do {
try self?.privateContext.save()
completion?(.saved)
}
catch {
self?.privateContext.rollback()
completion?(.rolledBack)
}
}
}
class ViewController: UIViewController {
@objc
func save(_ sender: UIBarButtonItem) {
let coreDataStack = CoreDataStack()
let tempMainContext = coreDataStack.createTemporaryViewContext() //child main context from private context
var people = People(context: tempMainContext)
self.people.name = "John Doe"
self.people.age = 25
coreDataStack.saveTempViewContext(tempContext: tempMainContext) { status in
print(status)
}
}
}
I have "privateContext" attached to coordinator
I've created "tempMainContext" from private context
When I call "saveTempViewContext" I want to save tempMainContext which pushes changes to parent (privateContext) and this privateContext saves to persistent store
So the error occurred in line
privateContext.hasChanges
I know thats this line executes in main thread. And I need to call method "perform" or "performAndWait" to perform on the right queue.
like this
var contextHasChanges: Bool = false
var privateContextHasChanges: Bool = false
context.performAndWait {
contextHasChanges = context.hasChanges
}
privateContext.performAndWait {
privateContextHasChanges = privateContext.hasChanges
}
guard context.hasChanges || privateContext.hasChanges else {
completion?(.hasNoChanges)
return
}
But it so weird to call "performAndWait" just to check that context has changes. And when I call "performAndWait" it block current thread in my case it MainThread. And I don't want block the main thread even for short time.
How could we resolve this issue ?
UPD 2
In my CoreDataStack init method in below I'v added code. I just check if the private context has changes and It will crash
let privateContextHasChanges = privateContext.hasChanges
I think it's because at that line we are in MainThread and we touch private context which init with "privateQueueConcurrencyType" and I investigate that if I touch other property for example "privateContext.name" or "privateContext.parent" it works fine.
But if I touch property like this:
privateContext.hasChanges
privateContext.registeredObjects
privateContext.updatedObjects
maybe other
it will crash again
So I can make a conclusion that these properties are not thread safe.
Can anyone confirm that ?
UPD 3 After I'v read post Unexpected Core Data Multithreading Violation
I'v made conclusions:
- If I'm on the main thread and my context's type is .main I do not need any changes and I safe
- If I'm on some place and I don't know want kind of thread I'm on I always need to do "perform" or "performAndWait" to synchonize queue attached to context.
- Almost always do "perform" and not "performAndWait" except you can't do without it.