【发布时间】:2020-05-23 12:45:58
【问题描述】:
我有 CoreDataStack
我为 CoreData 调试添加了调试选项 "-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)
}
}
}
我已将“privateContext”附加到协调器
我从私有上下文创建了“tempMainContext”
当我调用“saveTempViewContext”时,我想保存将更改推送到父级 (privateContext) 的 tempMainContext,并且此 privateContext 保存到持久存储中
所以错误发生在一行
privateContext.hasChanges
我知道这行在主线程中执行。而且我需要调用方法“perform”或“performAndWait”在正确的队列上执行。
喜欢这个
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
}
但是调用“performAndWait”只是为了检查上下文是否发生了变化,这太奇怪了。当我调用“performAndWait”时,它会阻塞当前线程,在我的情况下它是 MainThread。而且我不想阻塞主线程,即使是很短的时间。
我们如何解决这个问题?
UPD 2
在下面的 CoreDataStack 初始化方法中,我添加了代码。 我只是检查私有上下文是否有变化,它会崩溃
let privateContextHasChanges = privateContext.hasChanges
我认为这是因为在那一行我们在 MainThread 并且我们接触了以“privateQueueConcurrencyType”初始化的私有上下文,我调查如果我接触其他属性,例如“privateContext.name”或“privateContext.parent”它工作正常.
但是如果我像这样触摸财产:
privateContext.hasChanges
privateContext.registeredObjects
privateContext.updatedObjects
也许其他
它会再次崩溃
所以我可以得出结论,这些属性不是线程安全的。
谁能确认一下?
更新 3 在我阅读帖子Unexpected Core Data Multithreading Violation
我已经得出结论:
- 如果我在主线程上并且我的上下文类型是 .main 我不需要任何更改并且我很安全
- 如果我在某个地方并且我不知道自己想要哪种线程,我总是需要执行“perform”或“performAndWait”来同步附加到上下文的队列。
- 几乎总是“执行”而不是“执行和等待”,除非你不能没有它。
【问题讨论】:
-
你能附上崩溃堆栈跟踪吗?
-
我添加了一个带有崩溃堆栈的 img
-
“hasChanges”是线程安全的吗?