首页 > 解决方案 > CoreData 错误:API 滥用:尝试在非拥有协调器上序列化存储访问

问题描述

注意:
这篇文章不适用,因为我实际上使用的是 CoreData。
这篇文章中,最后一个答案建议在添加新对象之前获取新后台线程中的所有项目,但这是在我的代码中完成的。
这篇文章建议在保存项目的上下文之前对其进行无故障处理,但这也在我的代码中完成。

我的应用程序使用 CoreData 来存储名为shoppingItems. 我编写了一个CoreDataManager初始化 CoreData 的类,它本质上具有一个函数来覆盖当前存储的项目,以及一个函数来获取所有项目。这两个函数都在后台运行,即在单独的线程上运行。

这是我的代码(不相关的部分被省略了)。

我在主线程上设置核心数据:

private lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: modelName)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    })
    return container
}()  

这是写函数:

func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: @escaping (Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            // Delete currently stored shopping items
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
            do {
                let result = try backgroundContext.fetch(fetchRequest)
                let resultData = result as! [NSManagedObject]
                for object in resultData {
                    backgroundContext.delete(object)
                }

                if !shoppingItems.isEmpty {
                    // Save shopping items in managed context
                    let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
                    for nextShoppingItem in shoppingItems {
                        let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
                        nextCdShoppingItem.name = nextShoppingItem.name
                    }
                }

                let saveError = self.saveManagedContext(managedContext: backgroundContext)
                completion(saveError)
            } catch let error as NSError {
                completion(error)
            }
        }
}

func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
    if !managedContext.hasChanges { return nil }
    do {
        try managedContext.save()
        return nil
    } catch let error as NSError {
        return error
    }
}

这是 fetch 函数:

func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
            do {
                let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
                guard !cdShoppingItems.isEmpty else {
                    completion([], nil)
                    return
                }
                for nextCdShoppingItem in cdShoppingItems {
                }
                    completion(shoppingItems, nil) 
            } catch let error as NSError {
                completion(nil, error)
            }
        }
}  

在正常操作中,代码似乎可以工作。

问题:

我还编写了一个单元测试,试图引发多线程问题。此测试使用并发调度队列:

let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)  

计时器定义了测试时间。
在测试方案中,我设置了参数-com.apple.CoreData.Logging.stderr 1-com.apple.CoreData.ConcurrencyDebug 1.
在测试期间,overwriteShoppingItemsfetchShoppingItems被重复插入队列,并发执行。
这个单元测试执行一些读取和写入,然后停止在该行

                    let itemName = nextCdShoppingItem.name!  

因为nextCdShoppingItem.namenil,这不应该发生,因为我从不存储nil

在崩溃之前,立即记录以下内容:

CoreData: error:  API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)

如果我只获取或只写入,则不会记录 CoreData 警告。因此,这似乎肯定是一个多线程问题。但是,CoreData.ConcurrencyDebug没有检测到它。

看起来好像在一个线程上的获取操作期间,另一个线程删除了当前获取的项目,因此它的属性被读回为nil. 但这不应该发生,因为获取和保存是用 完成的backgroundContext.performAndWait,即串行的。堆栈跟踪显示只有一个线程访问 CoreData:Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)

我的问题:

编辑:

也许这有助于识别问题:当我在 中注释掉时backgroundContext.delete(object)overwriteShoppingItems不再记录错误,并且没有项目被提取为nil.

标签: iosmultithreadingcore-data

解决方案


如果您没有在项目中使用核心数据,请检查您的资产文件是否正确。

我也收到这样的错误,然后我从我的项目中发现了这个错误。


推荐阅读