用于核心数据操作的 NSOperation

NSOperation for Core Data operations

我的应用程序进行了大量从某种格式到 NSManagedObject 的大量转换,所以对我来说最好的方法是使用 NSOperation(或 Operation in Swift 3 ) 将原始数据转换为 NSManagedObject,并在所有操作完成后保存该上下文。

我不能为每个 Operation 使用单独的上下文,因为我的转换器生成关系(并且它们只能从相同的上下文访问)并且应用程序可能 运行 最多 20 次转换,所以在每次转换后创建新的上下文并保存它并不酷。

所以我需要创建单独的 OperationQueue 并确保其中的所有操作都是从与上下文相同的线程执行的,我不知道该怎么做。

我只有一个想法:将 Operation.main() 中的所有内容都作为 context.perform { } 启动,但我真的不认为这是一个好的解决方案。

我找到了 similar thread at Whosebug,但答案已经过时,我发现接受的答案显然不正确。

使用您自己的操作队列不是最好的方法。对于 Core Data,您需要通过 mainQueueConcurrencyTypeprivateQueueConcurrencyType 使用内置的 Core Data 并发,然后使用 performperformAndWait。使用您描述的自定义操作队列将无法正常工作。

这将使用由 Core Data 管理的调度队列。您无法直接计算未决操作的数量,但您可以一次添加一堆块,最后一个块在导入过程完成时执行任何需要发生的事情。在导入过程中保存而不是仅在最后保存也是一个好主意,以防止内存使用失控,除非您只导入少量数据。

我发现在 Operation 中使用核心数据的最佳方法是使用 privateQueueConcurrencyType 创建子上下文,而无需任何额外的 .perform 块(私有操作已经在需要线程)。我愿意接受任何其他建议。

我已经使用 operationQueue.maxConcurrentOperationCount = 1 来确保安全和没有合并冲突,但我可能会建议该方法适用于并发操作,但在大多数情况下它是无用的,因为操作会相互等待上下文未合并。

在与 parentContext 相同的线程中使用 waitUntilAllOperationsAreFinished() 时要小心,在大多数情况下会导致死锁。

示例代码

class ExampleOperation: Operation {
    let parentContext: NSManagedObjectContext

    init(parentContext: NSManagedObjectContext) {
        self.parentContext = parentContext
        super.init()
    }

    override func main() {
        if self.isCancelled { return }

        let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        childContext.parent = parentContext

        // use here `childContext` context directly
        // e.g.: let result = try childContext.fetch(fetchRequest)

        try? childContext.save()
}