如何从后台线程执行 CoreData context.count?

Che*_*eng 1 core-data ios swift

目前,以下是我关于 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我处理核心数据的策略是

  1. 要从主线程(UI 线程)执行非阻塞写入调用,我将使用CoreDataStack.INSTANCE.backgroundContext
  2. 要从主线程(UI 线程)执行阻塞读取调用,我将使用CoreDataStack.INSTANCE.persistentContainer.viewContext

这一直工作正常,直到我需要执行以下操作

  • 从后台线程(非 UI 线程)执行阻塞读取调用

PHPickerViewControllerDelegate我们需要运行的回调中的代码loadFileRepresentation。如果我们在回调Thread.isMainThread中检查 using (returns false) loadFileRepresentation,它会在后台线程中执行。

NSAttachmentRepository.INSTANCE.isExist(name)当我在 false 的函数中执行调用时Thread.isMainThread,我会遇到以下崩溃

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
}
Run Code Online (Sandbox Code Playgroud)

但是,我仍然遇到相同的崩溃错误。

您知道如何从后台线程执行 CoreData context.count 吗?

Tom*_*ton 5

仅使用背景上下文是不够的。您需要在其自己的队列上使用该上下文。您检查过您没有在主队列上运行,但您可以在任何队列上运行,并且后台上下文仅适用于其中之一。当您在错误的队列上使用 Core Data 时,您看到的错误消息就是 Core Data 所显示的内容。

每当您使用 时,您都需要将代码包装在对或 的backgroundContext调用中,以确保您的代码在后台上下文的队列上运行。由于您的函数是同步的,因此需要使用它才能在返回之前获得结果。performperformAndWaitisExistperformAndWait