Search code examples
objective-cswiftcore-datansoperationnsoperationqueue

NSOperation for Core Data operations


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.


Solution

  • 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()
    }