如何从后台线程执行 CoreData context.count?
How to perform CoreData context.count from a background thread?
目前我对CoreData的实现如下
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return backgroundContext
}()
}
class NSAttachmentRepository {
static let INSTANCE = NSAttachmentRepository()
private init() {
}
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
}
我处理核心数据的策略是
- 要从主线程(UI 线程)执行非阻塞写入调用,我将使用
CoreDataStack.INSTANCE.backgroundContext
- 要从主线程(UI 线程)执行阻塞读取调用,我将使用
CoreDataStack.INSTANCE.persistentContainer.viewContext
这一直都很好,直到我需要执行以下操作
- 从后台线程(非 UI 线程)执行阻塞读取调用
我们需要 运行 PHPickerViewControllerDelegate
的 loadFileRepresentation
回调中的代码。如果我们在 loadFileRepresentation
回调中使用 Thread.isMainThread
(returns false) 检查,它将在后台线程中执行。
当我在 Thread.isMainThread
为 false 的函数中执行调用 NSAttachmentRepository.INSTANCE.isExist(name)
时,我会遇到以下崩溃
CoreData`+[NSManagedObjectContext
Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
我尝试通过将代码从使用 coreDataStack.persistentContainer.viewContext
修改为 coreDataStack.backgroundContext
来“解决”问题
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
////let viewContext = coreDataStack.persistentContainer.viewContext
let backgroundContext = coreDataStack.backgroundContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
////let count = try viewContext.count(for: fetchRequest)
let count = try backgroundContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
但是,我仍然遇到同样的崩溃错误。
你知道我如何从后台线程执行 CoreData context.count 吗?
仅使用背景上下文是不够的。您需要在其自己的队列上使用该上下文。您检查过您不在主队列中 运行,但您可以在任何队列中,并且后台上下文仅适用于其中一个队列。当您在错误的队列上使用它时,您看到的错误消息是 Core Data 所说的。
任何时候使用 backgroundContext
,您都需要将代码包装在对 perform
或 performAndWait
的调用中,以确保您的代码在后台上下文的队列中运行。由于您的 isExist
函数是同步的,因此它需要使用 performAndWait
以便在返回之前可以得到结果。
感谢@Tom Harrington 的指点
下面是有关如何解决此类线程问题的代码片段。
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
var result = false
viewContext.performAndWait {
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
result = true
} else {
result = false
}
} catch {
error_log(error)
}
}
return result
}
目前我对CoreData的实现如下
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return backgroundContext
}()
}
class NSAttachmentRepository {
static let INSTANCE = NSAttachmentRepository()
private init() {
}
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
}
我处理核心数据的策略是
- 要从主线程(UI 线程)执行非阻塞写入调用,我将使用
CoreDataStack.INSTANCE.backgroundContext
- 要从主线程(UI 线程)执行阻塞读取调用,我将使用
CoreDataStack.INSTANCE.persistentContainer.viewContext
这一直都很好,直到我需要执行以下操作
- 从后台线程(非 UI 线程)执行阻塞读取调用
我们需要 运行 PHPickerViewControllerDelegate
的 loadFileRepresentation
回调中的代码。如果我们在 loadFileRepresentation
回调中使用 Thread.isMainThread
(returns false) 检查,它将在后台线程中执行。
当我在 Thread.isMainThread
为 false 的函数中执行调用 NSAttachmentRepository.INSTANCE.isExist(name)
时,我会遇到以下崩溃
CoreData`+[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
我尝试通过将代码从使用 coreDataStack.persistentContainer.viewContext
修改为 coreDataStack.backgroundContext
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
////let viewContext = coreDataStack.persistentContainer.viewContext
let backgroundContext = coreDataStack.backgroundContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
////let count = try viewContext.count(for: fetchRequest)
let count = try backgroundContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
但是,我仍然遇到同样的崩溃错误。
你知道我如何从后台线程执行 CoreData context.count 吗?
仅使用背景上下文是不够的。您需要在其自己的队列上使用该上下文。您检查过您不在主队列中 运行,但您可以在任何队列中,并且后台上下文仅适用于其中一个队列。当您在错误的队列上使用它时,您看到的错误消息是 Core Data 所说的。
任何时候使用 backgroundContext
,您都需要将代码包装在对 perform
或 performAndWait
的调用中,以确保您的代码在后台上下文的队列中运行。由于您的 isExist
函数是同步的,因此它需要使用 performAndWait
以便在返回之前可以得到结果。
感谢@Tom Harrington 的指点
下面是有关如何解决此类线程问题的代码片段。
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
var result = false
viewContext.performAndWait {
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
result = true
} else {
result = false
}
} catch {
error_log(error)
}
}
return result
}