1

My application makes a lot of heavy conversions from some format to NSManagedObject, so best way to me is to use NSOperation (or Operation in Swift 3) to make conversions from raw data to NSManagedObject and after all operations are finished save that context.

I can't use separate context for each Operation, because I my converter generates relationships (and they can be accessed only from same context) and application may run up to 20 conversions, so it's not cool to create new context and save it after each conversion.

So I need do create separate OperationQueue and ensure that all operations inside it are performed from same thread as context, I don't know how to do it.

I have only one mind: start everything inside Operation.main() as context.perform { }, but I don't really think that it is good solution.

I've found similar thread at Stackoverflow, but answers are outdated and I see that accepted answer is not clearly right.

Community
  • 1
  • 1
Vasily
  • 3,740
  • 3
  • 27
  • 61
  • Did you see this answer? http://stackoverflow.com/a/16178742/294949 – danh Feb 07 '17 at 19:28
  • @danh, yes, but NSManagedContext doesn't have counter for pending operations as I know, how can I know that all operations are completed? Do you think it will be safe just to make a lot of `.perform { }` and fire `.perform { try context.save() }` on last one? So it will run that save at the last. – Vasily Feb 07 '17 at 19:33
  • The operations queue has an `operationCount` property. Will that suffice? You could be notified when that goes to zero with KVO. – danh Feb 07 '17 at 19:43
  • @danh, context is not operation queue, you proposed to use context as NSOperation (like it that was in answer you provided). Or I don't understand the approach, can you post any example? – Vasily Feb 07 '17 at 20:18

2 Answers2

4

Using your own operation queue is not the best approach. With Core Data you need to use the built in Core Data concurrency via mainQueueConcurrencyType or privateQueueConcurrencyType, and then using perform or performAndWait. Using a custom operation queue as you describe will not work properly.

This will use dispatch queues managed by Core Data. You can't get a count of the number of pending operations directly but you can add a bunch of blocks at once with a final block that does whatever needs to happen when the import process is complete. It's also a good idea to save during the import process instead of only at the end, to keep memory use from getting out of hand, unless you're only importing a small amount of data.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
2

The best way I've found to use Core Data in Operation is to make child context with privateQueueConcurrencyType without any additional .perform blocks (that private operation is already created in needed thread). I'm open to any other suggestions.

I've used operationQueue.maxConcurrentOperationCount = 1 to ensure safety and absence of merging conflicts, but I may propose that approach will work with concurrent operations, but it will be useless in most cases because operations will wait each while other context is not merged.

Be carefull about using waitUntilAllOperationsAreFinished() in the same thread as parentContext, in most cases that will cause deadlock.

Example code

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()
}
Vasily
  • 3,740
  • 3
  • 27
  • 61