Even though Core Data is pretty performant, some operations can take a while to complete. When such an operation is performed on the main thread, it blocks the main thread. The result is an unresponsive user interface, which you want to avoid at any cost.

A popular feature of Samsara is its statistics summary. It gives the user an overview of their yoga and meditation sessions. To populate this summary, the application needs to carry out a series of fetch requests.

Samsara's Statistics Summary

This is not an issue if the user just started using the application and the persistent store is still small and manageable. But some people have been using Samsara for several years, resulting in pretty sizable databases.

SQLite is incredibly fast and I doubt that any of Samsara's users is going to run into performance issues. Nevertheless, it is a good idea to be prepared. There are several solutions to solve this problem. The solution I would like to discuss in this tutorial is asynchronous fetching using operations.

How Does It Work?

The idea is simple. Each fetch request is performed by an NSOperation instance. The operation uses a private managed object context to perform the fetch request in the background and it notifies a delegate when the results are ready to be displayed to the user.

The user can use the application while the data is being fetched. The user interface is updated incrementally, that is, whenever an operation is completed, the user interface is updated with the newly fetched data.

Building a Foundation

I have chosen to create a separate subclass for every fetch request. This keeps the class focused and easy to understand. Every subclass inherits from an NSOperation subclass that contains most of the logic for creating and preparing the operation for its task. Let me show you what that superclass looks like.

import UIKit
import CoreData

protocol CoreDataOperationDelegate {
    func operation(operation: CoreDataOperation, didCompleteWithResult: [String: AnyObject])
}

class CoreDataOperation: NSOperation {

    let delegate: CoreDataOperationDelegate
    let privateManagedObjectContext: NSManagedObjectContext

    var identifier = 0
    var result = [String: AnyObject]()

    // MARK: - Initialization

    init(delegate: CoreDataOperationDelegate, managedObjectContext: NSManagedObjectContext) {
        // Set Delegate
        self.delegate = delegate

        // Initialize Managed Object Context
        privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

        // Configure Managed Object Context
        privateManagedObjectContext.persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator

        super.init()
    }

    // MARK: - Perform Fetch Request

    func performFetchRequest() {}

    // MARK: - Overrides

    override func main() {
        performFetchRequest()

        dispatch_async(dispatch_get_main_queue()) {
            // Notify Delegate
            self.delegate.operation(self, didCompleteWithResult: self.result)
        }
    }

}

The idea is straightforward. We declare a delegate protocol with a single method. This method is invoked when the operation completes. The object interested in the result of the operation's fetch request is required to implement the method of this protocol.

protocol CoreDataOperationDelegate {
    func operation(operation: CoreDataOperation, didCompleteWithResult: [String: AnyObject])
}

The CoreDataOperation class inherits from NSOperation. It has two constant and two variable properties. The constant properties are the delegate object and the private managed object context that is used to preform the fetch request. This is a very important aspect of the CoreDataOperation class. We need to use a private managed object context to perform the fetch request on a background queue.

The variable properties are an identifier of type Int and a dictionary of type [String: AnyObject] to store the result or error of the fetch request in. The identifier is used to identify an operation in the operation queue. This becomes clear in a moment.

class CoreDataOperation: NSOperation {

    let delegate: CoreDataOperationDelegate
    let privateManagedObjectContext: NSManagedObjectContext

    var identifier = 0
    var result = [String: AnyObject]()

    ...
}

The initializer is pretty simple. It accepts two arguments, the delegate object and the managed object context that is used to create the private managed object context. Setting up the private managed object context is straightforward. We set the concurrency type to PrivateQueueConcurrencyType and use the managed object context to obtain a reference to the persistent store coordinator.

// MARK: - Initialization

init(delegate: CoreDataOperationDelegate, managedObjectContext: NSManagedObjectContext) {
    // Set Delegate
    self.delegate = delegate

    // Initialize Managed Object Context
    privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

    // Configure Managed Object Context
    privateManagedObjectContext.persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator

    super.init()
}

Every subclass of the CoreDataOperation class needs to override the performFetchRequest() method. In this method, the operation fetches the data it is interested in from the persistent store. The implementation is empty in the CoreDataOperation class.

// MARK: - Perform Fetch Request

func performFetchRequest() {}

The most interesting method of the CoreDataOperation class is the main() method. In this method, we perform the fetch request and notify the delegate of the result of the operation. The delegate object is notified on the main thread. This is not essential, but it is convenient for anyone using the CoreDataOperation class.

// MARK: - Overrides

override func main() {
    performFetchRequest()

    dispatch_async(dispatch_get_main_queue()) { 
        // Notify Delegate
        self.delegate.operation(self, didCompleteWithResult: self.result)
    }
}

Performing a Fetch Request

With the CoreDataOperation class ready to use, it is time to create a subclass that fetches data from the persistent store. The implementation can be as simple or as complex as you like. This is what one of the subclasses looks like. The operation fetches every session of the user and calculates the average session time.

import UIKit

class AverageTimeInSessionOperation: CoreDataOperation {

    override func performFetchRequest() {
        if let sessions = Session.findAllInManagedObjectContext(privateManagedObjectContext) as? [Session] {
            // Helprs
            var average: Double = 0.0
            var timeInSession: Double = 0.0
            let numberOfSessions = sessions.count

            if numberOfSessions > 0 {
                for session in sessions {
                    timeInSession += session.duration.doubleValue
                }

                // Average Time In Session
                average = (timeInSession / Double(numberOfSessions));
            }

            // Update Result
            result["result"] = NSNumber(double: average)
        }
    }

}

The CoreDataOperation subclass is appropriately named AverageTimeInSessionOperation. The class only overrides the performFetchRequest() method. In this method, we fetch every session in the persistent store and calculate the total time the user has spent meditating. That number is divided by the number of sessions.

We store the result of the operation in the result dictionary. The reason this object is a dictionary is simple. The code was originally written in Objective-C and I have ported it to Swift for this tutorial. It may be more elegant to use a tuple, for example. Some operations may return multiple values and it is also convenient for storing errors in. You could also pass an optional error object as an argument to the delegate method we defined earlier. That is entirely up to you.

Scheduling Operations

Let me end this tutorial by showing you how to schedule a CoreDataOperation operation. We instantiate an instance of the AverageTimeInSessionOperation class, passing in a delegate and a managed object context. We set the operation's identifier and add it to an operation queue. That is it.

// Initialize Operation
let operation = AverageTimeInSessionOperation(delegate: self, managedObjectContext: managedObjectContext)

// Configure Operation
operation.identifier = 1

// Add Operation to Operation Queue
operationQueue.addOperation(operation)

In the delegate method of the CoreDataOperationDelegate protocol, we can process the result and update the user interface.

func operation(operation: CoreDataOperation, didCompleteWithResult: [String: AnyObject]) {
    // Update User Interface
    ...
}

Exploring Operations

The NSOperation class may seem daunting and obscure at first, but it is certainly a class worth exploring. You can take your first steps with operations by using NSBlockOperation, a concrete NSOperation subclass that accepts and executes a block or closure. This is a good starting point to become familiar with operations, operation queues, and asynchronous programming.

Questions? Leave them in the comments below or reach out to me on Twitter.