Contents

CoreData: CRUD With Concurrency In Swift - Part 1


Do you have performance issues with your CoreData implementation because you’re blocking the main thread with some computations? This series can help you to solve your problems and improve the user experience of your App. First Part: Create.

Introduction

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 to support also 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!

Getting Started

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. No 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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:

1
2
3
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "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 dogs' name as elements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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 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.

iOS 10+

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:

1
2
3
4
5
6
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 contexts are 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 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 parameter:

1
let dog = Dog(context: context)

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!