We successfully seeded the Core Data persistent store with hard-coded seed data in the previous episode. While the implementation works fine, it isn't perfect. Seeding the persistent store takes place on the main thread. We invoke the seed() method in the viewDidLoad() method of the NotesViewController class and we insert the managed objects into the main managed object context of the Core Data manager.
We refactor the implementation in this episode. The goal is to move as much work as possible to a background thread. This should help prevent the application from becoming unresponsive as long as it's seeding the persistent store with data.
Operations
Operations are ideal for performing tasks on a background thread. The idea is straightforward. We create an operation responsible for seeding the persistent store with data and we perform the operation on a background thread.
We won't be using the main managed object context to seed the persistent store with data. We create a private managed object context in the operation with the main managed object context of the Core Data manager as its parent. This is an application of nested managed object contexts, a concept we discuss in detail in Core Data Fundamentals.
Let's get to work. Download the starter project of this episode to follow along. We create a new file in a group named Operations. We choose the Cocoa Touch Class template from the list of iOS > Source templates.

We set Subclass of to Operation to create an Operation subclass. Let's name the class SeedOperation.

In SeedOperation.swift, we add an import statement for the Foundation framework and the Core Data framework. The Operation class is defined in the Foundation framework.
import CoreData
import Foundation
class SeedOperation: Operation {
}
The next step is defining a property for the managed object context and a designated initializer for the SeedOperation class. We name the property privateManagedObjectContext and it should be of type NSManagedObjectContext. Notice that the property is a constant private property. Because the privateManagedObjectContext property is defined as a constant, we need to set its value during initialization. We do this in a designated initializer.
import CoreData
import Foundation
class SeedOperation: Operation {
// MARK: - Properties
private let privateManagedObjectContext: NSManagedObjectContext
}
The compiler throws an error, which means it's time to implement the designated initializer. The initializer accepts an argument of type NSManagedObjectContext.
import CoreData
import Foundation
class SeedOperation: Operation {
// MARK: - Properties
private let privateManagedObjectContext: NSManagedObjectContext
// MARK: - Initialization
init(with managedObjectContext: NSManagedObjectContext) {
// Initialize Managed Object Context
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Configure Managed Object Context
privateManagedObjectContext.parent = managedObjectContext
super.init()
}
}
In the initializer, we create a managed object context by passing privateQueueConcurrencyType to the designated initializer of the NSManagedObjectContext class. We assign the managed object context to the privateManagedObjectContext property.
We set the parent property of the private managed object context to the managed object context that is passed to the initializer of the operation. Last but not least, we invoke the designated initializer of the superclass of SeedOperation.
Seeding Data
Operations perform their work in the main() method. We override this method and invoke seed(), a helper method. I prefer to keep the implementation of the main() method short and simple.
// MARK: - Overrides
override func main() {
// Seed With Data
seed()
}
Let's create a stub for the seed() method to keep the compiler happy.
// MARK: - Helper Methods
private func seed() {
}
Before we implement the seed() method, we need to move the hard-coded seed data. We won't burden the CoreDataManager class with this task. Create a new group in the Core Data group and name it Seed Data. Add a new Swift file to this group and name it Seed.swift.

In this file, we define a structure, Seed, that is responsible for providing other objects access to the seed data.
import Foundation
struct Seed {
}
Implementing the Seed structure couldn't be easier. We move the seed data from the CoreDataManager class to the Seed structure. The only change we need to make is marking the computed properties as internal by removing the private keywords.
import Foundation
struct Seed {
// MARK: - Seed Data
var tags: [String] {
return [
"Tips and Tricks",
...
"News"
]
}
var categories: [(name: String, colorAsHex: String)] {
return [
(name: "Core Data", colorAsHex: "F6D14E"),
...
(name: "Objective-C", colorAsHex: "4483F3")
]
}
var notes: [(title: String, createdAt: Double, updatedAt: Double, category: String, tags: [String])] {
return [
(title: "Getting Started With Core Data", createdAt: 1518011139, updatedAt: 1518011139, category: "Swift", tags: ["Tips and Tricks"]),
...
(title: "Core Data Fundamentals", createdAt: 1517794761, updatedAt: 1517794761, category: "Swift", tags: ["News"])
]
}
var contents: [String] {
return [
"Lorem ipsum dolor sit amet ...",
...
"Aliquam sagittis magna felis ..."
]
}
}
The implementation of the seed() method of the SeedOperation class is almost identical to that of the CoreDataManager class. Let's move the seed() method of the CoreDataManager class to the SeedOperation class and find out what needs to change.
Loading Seed Data
Because the seed data is provided by the Seed structure, we start by creating an instance of the Seed structure.
func seed() {
// Initialize Seed Data
let seed = Seed()
...
}
Any references to the seed data need to be updated because we ask the seed object for the seed data.
func seed() {
// Initialize Seed Data
let seed = Seed()
...
for name in seed.tags {
...
}
for data in seed.categories {
...
}
for (index, data) in seed.notes.enumerated() {
...
note.contents = seed.contents[index]
...
}
}
The compiler complains that the SeedOperation class doesn't have a mainManagedObjectContext property. We need to replace the references to the mainManagedObjectContext property with references to the privateManagedObjectContext property of the SeedOperation class.
// MARK: - Helper Methods
func seed() {
// Initialize Seed Data
let seed = Seed()
// Helpers
var tagsBuffer: [Tag] = []
var categoriesBuffer: [Category] = []
for name in seed.tags {
// Initialize Tag
let tag = Tag(context: privateManagedObjectContext)
// Configure Tag
tag.name = name
// Append to Buffer
tagsBuffer.append(tag)
}
for data in seed.categories {
// Initialize Category
let category = Category(context: privateManagedObjectContext)
// Configure Category
category.name = data.name
category.colorAsHex = data.colorAsHex
// Append to Buffer
categoriesBuffer.append(category)
}
for (index, data) in seed.notes.enumerated() {
// Initialize Note
let note = Note(context: privateManagedObjectContext)
// Configure Note
note.title = data.title
note.contents = seed.contents[index]
note.createdAt = Date(timeIntervalSince1970: data.createdAt)
note.updatedAt = Date(timeIntervalSince1970: data.updatedAt)
// Add Category
note.category = categoriesBuffer.first {
return $0.name == data.category
}
// Helpers
let tagsAsSet = Set(data.tags)
// Add Tags
for tag in tagsBuffer {
guard let name = tag.name else {
continue
}
if tagsAsSet.contains(name) {
note.addToTags(tag)
}
}
}
do {
// Save Changes
try privateManagedObjectContext.save()
} catch {
print("Unable to Save Main Managed Object Context After Seeding Persistent Store (\(error))")
}
}
Completion Handler
To make the SeedOperation class easy to use, I'd like to define a completion handler that is invoked when the seed operation has completed. We define a type alias at the top for convenience. The completion handler won't accept any arguments and returns nothing.
import CoreData
import Foundation
class SeedOperation: Operation {
// MARK: - Type Alias
typealias SeedOperationCompletion = (() -> Void)
// MARK: - Properties
private let privateManagedObjectContext: NSManagedObjectContext
...
}
You can adapt this to the needs of your project. You could, for example, pass an optional Error object to the closure if you want to propagate errors that are thrown in the operation. I'm keeping it simple in this example.
We declare a private property, completion, of type SeedOperationCompletion?. Notice that completion is of an optional type. If the consumer of the SeedOperation API isn't interested in the completion of the operation, it can ignore the completion handler.
import CoreData
import Foundation
class SeedOperation: Operation {
// MARK: - Type Alias
typealias SeedOperationCompletion = (() -> Void)
// MARK: - Properties
private let privateManagedObjectContext: NSManagedObjectContext
// MARK: -
private let completion: SeedOperationCompletion?
...
}
We pass the completion handler to the designated initializer as an argument. Notice that the default value of the argument is nil. We set the completion property in the initializer before invoking the initializer of the superclass.
// MARK: - Initialization
init(with managedObjectContext: NSManagedObjectContext, completion: SeedOperationCompletion? = nil) {
// Initialize Managed Object Context
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Configure Managed Object Context
privateManagedObjectContext.parent = managedObjectContext
// Set Completion
self.completion = completion
super.init()
}
We invoke the completion handler in the main() method, after the seed() method. Because the completion property is of an optional type, we use optional chaining.
// MARK: - Overrides
override func main() {
// Seed With Data
seed()
// Invoke Completion
completion?()
}
Updating the Core Data Manager
It's time to update the CoreDataManager class and put the SeedOperation class to use. We define a method, seed(_:), that optionally accepts a closure. We pass this closure to the designated initializer of the SeedOperation class.
// MARK: - Public API
func seed(_ completion: (() -> Void)? = nil) {
// Initialize Operation
let operation = SeedOperation(with: mainManagedObjectContext, completion: completion)
}
To perform the operation, we need to add it to an operation queue. Let's define an operation queue in the CoreDataManager class. Open CoreDataManager.swift and declare a constant property, operationQueue. We create and assign an OperationQueue instance to the operationQueue property.
import CoreData
final class CoreDataManager {
// MARK: - Properties
private let modelName: String
// MARK: -
let operationQueue = OperationQueue()
...
}
Head back to CoreDataManager+Seed.swift and add the seed operation to the operation queue of the Core Data Manager.
// MARK: - Public API
func seed(_ completion: (() -> Void)? = nil) {
// Initialize Operation
let operation = SeedOperation(with: mainManagedObjectContext, completion: completion)
// Add to Operation Queue
operationQueue.addOperation(operation)
}
Updating the Notes View Controller
We don't need to update the notes view controller if we don't want to pass a completion handler to the seed(_:) method of the Core Data manager. But let's make sure everything is working by invoking the seed(_:) method with a completion handler and print a message to the console.
// MARK: - View Life Cycle
override func viewDidLoad() {
...
// Seed Persistent Store
coreDataManager.seed {
print("Seed Operation Completed")
}
}
Build and Run
Remove Notes from your device or the simulator, and install a clean build. Because we're using a fetched results controller to populate the table view of the notes view controller, the table view is automatically populated with the seed data the moment the seed operation completes.

We should also see a message in Xcode's console, telling us that the seed operation has completed.
Seed Operation Completed
The completion handler is a nice addition. It allows you, for example, to display a loading indicator as long as the seed operation is running. This may be convenient if you don't want the user to perform certain actions as long as the seed operation is in progress.
What's Next?
Our implementation to seed the persistent store with seed data has improved quite a bit without adding a lot of complexity. The Core Data manager is no longer in charge of seeding the persistent store with data. That's another benefit I like.
Before we replace hard-coded seed data with data loaded from a file or backend, I'd like to make a few small tweaks to avoid duplicate records. This is especially useful in development. Let's do that in the next episode.