ios - CoreData 多线程获取在访问属性之前需要很小的延迟

标签 ios multithreading core-data

我使用 coreData 作为持久存储。
为了读取数据,我使用(仅显示基本部分):

func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
    persistentContainer.performBackgroundTask { (backgroundManagedContext) in
        let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
        var shoppingItems: Set<ShoppingItem> = []
        do {
            let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
            for nextCdShoppingItem in cdShoppingItems {
                nextCdShoppingItem.managedObjectContext!.performAndWait {
                    Thread.sleep(forTimeInterval: 0.1) // This seems to be required
                    let nextShoppingItem = ShoppingItem.init(name: nextCdShoppingItem.name!)
                    shoppingItems.insert(nextShoppingItem)
                } // performAndWait
            } // for all cdShoppingItems
            completion(shoppingItems, nil)
            return
        } catch let error as NSError {
            completion(nil, error)
            return
        } // fetch error
    } // performBackgroundTask
} // fetchShoppingItems  

为了测试 coreData 实现,我编写了一个单元测试,该测试创建多个并发写入和读取 coreData 的线程。
仅当指令

时,此测试才能成功运行
Thread.sleep(forTimeInterval: 0.1) // This seems to be required

插入到performAndWait闭包中。
如果被注释掉,nextCdShoppingItem 通常会以 nil 属性读回,并且函数会因强制解包而崩溃。
我不确定 nextCdShoppingItem.managedObjectContext!.performAndWait 是否正确,或者我是否必须使用 backgroundManagedContext.performAndWait,但使用 backgroundManagedContext 效果是一样的。
我不明白为什么在访问托管对象的属性之前插入一个小的延迟是避免该问题所必需的。
欢迎任何提示!

编辑:

我进一步调查了该问题,发现以下内容:

每次 nextCdShoppingItem 被后台线程(下面称为读取线程)读回为 nil 时,还有另一个后台线程在之后尝试保存自己的 ManagedContext其 ManagedContext 中的所有记录均已被删除(下面称为写入线程)。
显然,读取线程尝试获取刚刚被写入线程删除的记录。
所以问题肯定是多线程问题,我找到了解决方案(请参阅下面的答案)。

最佳答案

performAndWait 会将 block 添加到队列中并安排其运行,就像执行一样,但performAndWait 在 block 完成之前不会返回。由于您处于 cdShoppingItems 循环内,因此循环不会停止并等待 block 返回。通过添加线程 sleep ,您实际上会减慢循环速度,并为核心数据提供足够的时间来完成其获取。强制展开崩溃可能表明它丢失了 nextCdShoppingItem 引用。

我会考虑重构,您不需要在循环内查询核心数据。如果可能,请将 name 属性添加到 CDShoppingItem,这样您就不必获取它来构建 ShoppingItem 对象。

编辑:尽管我不知道你的确切用例,但尝试了重构:

func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
    persistentContainer.performBackgroundTask { (backgroundManagedContext) in
        let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
        do {
            var shoppingItems: Set<ShoppingItem> = []
            let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
            for nextCdShoppingItem in cdShoppingItems {
                if let name = nextCdShoppingItem.name {
                    let nextShoppingItem = ShoppingItem.init(name: name)
                    shoppingItems.insert(nextShoppingItem)
                }
            }
            completion(shoppingItems, nil)
        } catch let error as NSError {
            print("Error fetching CDShoppingItem: \(error)")
            completion(nil, error)
        } // fetch error
        return
    } // performBackgroundTask
} // fetchShoppingItems

关于ios - CoreData 多线程获取在访问属性之前需要很小的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57083395/

相关文章:

ios - 如何固定图像在 UIButton 中的位置?

ios - 理解 iBeacon 数据 : the power field and other bytes

python - 从另一个线程调用线程中的方法,python

Java 多线程一次运行多个方法的最简单方法?

swift - 将数据从 viewcontroller tableview 传递到另一个 View Controller

ios - UIScrollView 交互区域不随 frame/contentSize 更新

ios - UIAlertView 和 UIActionSheet 按钮单击 - EXC_BAD_ACCESS

c++ - 如何在c++中创建不同数量的线程?

ios - 无法删除其他上下文中的对象

objective-c - UIManagedDocument 和其他内容