Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

CoreData: CRUD With Concurrency in Swift - Part 1

DZone's Guide to

CoreData: CRUD With Concurrency in Swift - Part 1

Keeping your iOS app humming along is important, and requires not blocking at unnecessary times. Learn how to perform CRUD operations using a background queue.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

This is the first part of the series CoreData: CRUD With Concurrency In Swift: CREATE.

Using CoreData with background queues is very important to avoid blocking the main one with heavy computations. For this reason, we should use concurrency most of the time. In this article, we are going to see how to save some objects with CoreData in a background queue.

CoreData received a major update with iOS 10. For this reason, in this series, I’ll explain how to achieve our goals in both pre and post iOS 10. You may be wondering: “iOS 10 has a very cool update of CoreData, why should I use the old and ugly way?” Well, I know after every WWDC we are keen to use the latest updates. Unfortunately, when we build an app, we should consider supporting iOS versions older than the new one, since there is a percentage of users who are still using old versions. Therefore, if we want to reach as many users as possible, we should support at least two versions older than the new one. Therefore, if we build with minimum version iOS 9 then we cannot use the CoreData of iOS 10. Easy-peasy.

Happy reading!

Before starting our journey, we have to create a data model—which we will use for all the further examples.

We can use a data model with just an entity Dog with a String attribute name—I know you wanted Cat. Not today, I prefer dogs.

iOS 8+

If we want to be compatible with versions older than iOS 10, the first thing which we should do is creating the CoreData stack manually:

// Name of the data model file
let dataModelName = "MyDatabaseName"

// Loads the model file from the bundle
guard let modelURL = Bundle.main.url(forResource: dataModelName, withExtension:"momd") else {
    fatalError("Error loading model from bundle")
}

// Loads the scheme of our database
guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else {
    fatalError("Error initializing mom from: \(modelURL)")
}

// Creates the persistent store coordinator
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

// Creates the main context
let mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

// Adds the persistent store in a background queue since it may be time consuming
DispatchQueue.global(qos: .background).async {
    // Adds the persistent store using a sqlite file
    let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let docURL = urls[urls.endIndex-1]
    let storeURL = docURL.appendingPathComponent("\(dataModelName).sqlite")
    do {
        try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
    } catch {
        fatalError("Error migrating store: \(error)")
    }
}

As we can see in the example above, we created the main NSManagedObjectContextmainManagedObjectContext—with the concurrency type mainQueueConcurrencyType. This means that this context performs any operation in the main queue. We need it to save the data properly in our database. As we can deduce, it’s not the right context for our background tasks.

Fortunately, CoreData allows us to create a context which can work in a background queue. This context will be a child of the main one, since we need the main one to update the data at the end of our computations.

We can create a child context with the following code:

let childManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Creates the link between child and parent
childManagedObjectContext.parent = mainManagedObjectContext

We can notice that the concurrency type is privateQueueConcurrencyType. It means that this context works in a background queue.

Now, we are ready to use the child context to keep the persistence of some objects Dog with CoreData.

Let’s consider that our application gets a list of Dogs from an API endpoint, which returns a JSON like:

{
  "dogs": [
    {
      id: 1,
      name: "Max"
    }, {
      id: 2,
      name: "Daisy"
    }, {
      id: 3,
      name: "Riley"
    }
  ]
}

In our application, we should send an API request, parse the JSON and keep the persistence of the data with CoreData. The whole process should be done in a background queue to avoid keeping the main queue blocked.

How to send the request and parse the data is up to you. Let’s focus on how to save our data:

For this example, we can consider that we parsed the JSON inside an array with the dog’s name as elements.

// Creates a background task
childManagedObjectContext.perform {
    // Parse inside this background task

    let dogsName = //["Max", "Daisy", "Riley"]

    // Iterates the array
    dogsName.forEach { name in
        // Creates a new entry inside the context `childManagedObjectContext` and assign the array element `name` to the dog's name
        let dog = NSEntityDescription.insertNewObject(forEntityName: "Dog", into: childManagedObjectContext)
        dog.name = name
    }

    do {
        // Saves the entries created in the `forEach`
        try childManagedObjectContext.save()

        // Performs a task in the main queue and wait until this tasks finishes
        mainManagedObjectContext.performAndWait {
            do {
                // Saves the data from the child to the main context to be stored properly
                try mainManagedObjectContext.save()
            } catch {
                fatalError("Failure to save context: \(error)")
            }
        }
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

This example shows two important functions which allow us to manage the concurrency with different contexts:

  • perform(_:): This is a context method which allows us to execute a closure asynchronously. We can use this method with both main and child context. If we use the main context—mainManagedObjectContext.perform {}—the closure will be executed in the main queue, otherwise with a child context—like in this example—the closure will be executed in a background queue.
  • performAndWait(_:): It’s the same of perform(_:) with the only difference that it executes the closure synchronously. It means that it blocks the queue until the closure is executed.

Note: When we save something in a child context, then we must also save it in the main one to store properly the data in the database. If we don’t save the data in the main one, then we won’t have the data available on the main context and in all its children but just in the child where we saved the data.

With iOS 10, Apple introduced an easier way to manage the CoreData stack: NSPersistentContainer.

NSPersistentContainer wraps the CoreData stack providing an high-level interface. In this way, we can avoid creating the stack manually but we can just instantiate this object like this:

let persistentContainer = NSPersistentContainer(name: "MyDatabaseName")
persistentContainer.loadPersistentStores { (_, error) in
    if let error = error {
        fatalError("Failed to load Core Data stack: \(error)")
    }
}

With this new way, we can create a child context without linking it to the main one, but just calling the method newBackgroundContext(). Every context is directly linked to the persistent store coordinator. This means that, if we save something in a background context, then we don’t need to save manually in the main one. It will be automatic.

For our example, we don’t need to create a background context manually and call perform. We can merely use the method performBackgroundTask. This method executes a closure in a background queue providing a background context as closure argument.

At this point, we are ready to save our dogs with a persistent container:

// Creates a task with a new background context created on the fly
persistentContainer.performBackgroundTask { (context) in
    // Iterates the array
    dogsName.forEach { name in
        // Creates a new entry inside the context `context` and assign the array element `name` to the dog's name
        let dog = Dog(context: context)
        dog.name = name
    }

    do {
        // Saves the entries created in the `forEach`
        try context.save()
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

As you may have noticed, we can create a new NSManagedObject object using its new constructor with the context as a parameter:

Conclusion

That’s all for our first adventure in the CoreData concurrency world. In the next article, we’ll see how to read the data in a background queue. Stay tuned!

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
data ,mobile ,crud ,ios ,mobile app development

Published at DZone with permission of Marco Santarossa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}