I am the author of Core Data Fundamentals with Swift, CloudKit, iOS Data Persistence: The Big Picture, and eight other courses on Pluralsight.
Deepen your understanding by watching the courses!
Using Swift to Seed a Core Data Database
Designing an application’s UI can be difficult without actual data to present. Early on in the design process, data sourced from something like an array can suffice. Later on, however, our data sources become more dependent on actual data stores, such as what Core Data provides us.
During development, I’ve found that it’s often convenient to seed a Core Data database with sample data so that I can preview how it’ll look in my application. Along with that, it’s nice to start with a fresh copy of the data each time I run the app. Let’s explore how to accomplish this task in Swift!
Scenario setup
I’ve created a fictitious Core Data app called “Zootastic”, which is intended to model zoos, and the animals that each zoo cares for. (I’ll give you one guess as to what kinds of shows and activities my two year old and I have been in to lately). :]
Specifically, I have the following Entities:
- Zoo
- name
- location
- animals (relationship)
- Animal
- commonName
- habitat
- classification (relationship)
- zoos (relationship)
- Classification
- scientificClassification
- family
- order
- animals (relationship)
I’ve created NSManagedObject subclasses for each of my entities, to make it easier to set properties.
DataHelper.swift
Once a Core Data data model is set up, we’re ready to create what I called DataHelper
. It serves the purpose of seeding the data store, and logging the data store’s contents back out to the console. It violates the single-responsibility principle, but wait! Don’t lynch me!
Knowing that this class is intended to be used solely for development, I didn’t put a lot of effort into separating concerns. I opted for a “here’s where I go to do all my seeding for manual testing purposes” approach.
Here are a few snippets from the class (grab the full Xcode project over at GitHub):
Initialization
1public class DataHelper {
2 let context: NSManagedObjectContext
3
4 init(context: NSManagedObjectContext) {
5 self.context = context
6 }
7
8// ...
9
10}
The primary thing to take away from the initialization routine is that instead of calling out to the AppDelegate to get an instance of the NSManagedObjectContext
, I’m choosing to require it to be passed in during the initialization of DataHelper
. It’s a pattern I like to practice because it allows me to do unit tests in real-world applications that use Core Data.
seedZoos()
1public class DataHelper {
2
3// ...
4
5 private func seedZoos() {
6 let zoos = [
7 (name: "Oklahoma City Zoo", location: "Oklahoma City, OK"),
8 (name: "Lowry Park Zoo", location: "Tampa, FL"),
9 (name: "San Diego Zoo", location: "San Diego, CA")
10 ]
11
12 for zoo in zoos {
13 let newZoo = NSEntityDescription.insertNewObjectForEntityForName("Zoo", inManagedObjectContext: context) as! Zoo
14 newZoo.name = zoo.name
15 newZoo.location = zoo.location
16 }
17
18 do {
19 try context.save()
20 } catch _ {
21 }
22 }
23
24// ...
25
26}
A few observations on the code above:
- This function may look a bit interesting. I’ve chosen to create an array of tuples that I loop over and use to extract actual
Zoo
information when I callNSEntityDescription.insertNewObjectForEntityForName()
. It just looked nice to me to have two chunks of code to analyze inside the function: One (the array of tuples) to see all of the zoo information I plan to insert, and another (the for-loop) to do the actual inserting into the data store. Adding moreZoo
s in the future would mean simply adding another tuple to the array. Convenient! - Since I’ve created NSManagedObject subclasses for my entities, I can cast the result of
NSEntityDescription.insertNewObjectForEntityForName()
to an actualZoo
, to work with the properties directly.
printAllZoos()
1public class DataHelper {
2
3// ...
4
5 public func printAllZoos() {
6 let zooFetchRequest = NSFetchRequest(entityName: "Zoo")
7 let primarySortDescriptor = NSSortDescriptor(key: "name", ascending: true)
8
9 zooFetchRequest.sortDescriptors = [primarySortDescriptor]
10
11 let allZoos = (try! context.executeFetchRequest(zooFetchRequest)) as! [Zoo]
12
13 for zoo in allZoos {
14 print("Zoo Name: \(zoo.name)\nLocation: \(zoo.location) \n-------\n", terminator: "")
15 }
16 }
17
18// ...
19
20}
printAllZoos()
function utilizes a standard NSFetchRequest
, along with an NSSortDescriptor
. Check out my cheat sheet for more examples of common Core Data operations.
Once again, having the NSManagedObject
subclass in place allows me to cast the result of the fetch request to a [Zoo]
.
seedAnimals()
1public class DataHelper {
2
3// ...
4
5 private func seedAnimals() {
6 // Grab Classifications
7 let classificationFetchRequest = NSFetchRequest(entityName: "Classification")
8 let allClassifications = (try! context.executeFetchRequest(classificationFetchRequest)) as! [Classification]
9
10 let manatee = allClassifications.filter({(c: Classification) -> Bool in
11 return c.family == "Trichechidae"
12 }).first
13
14 // Same pattern for monkey and bat
15
16 // Grab Zoos
17 let zooFetchRequest = NSFetchRequest(entityName: "Zoo")
18 let allZoos = (try! context.executeFetchRequest(zooFetchRequest)) as! [Zoo]
19
20 let oklahomaCityZoo = allZoos.filter({ (z: Zoo) -> Bool in
21 return z.name == "Oklahoma City Zoo"
22 }).first
23
24 // Same pattern for San Diego Zoo and Lowry Park Zoo
25
26 // Create an array of "animal" tuples, assigning
27 // whatever Classification and Zoo make sense
28 let animals = [
29 (commonName: "Pygmy Fruit-eating Bat", habitat: "Flying Mamals Exhibit", classification: bat!, zoos: NSSet(array: [lowryParkZoo!, oklahomaCityZoo!, sanDiegoZoo!])),
30 (commonName: "Mantled Howler", habitat: "Primate Exhibit", classification: monkey!, zoos: NSSet(array: [sanDiegoZoo!, lowryParkZoo!])),
31 (commonName: "Geoffroy’s Spider Monkey", habitat: "Primate Exhibit", classification: monkey!, zoos: NSSet(array: [sanDiegoZoo!])),
32 (commonName: "West Indian Manatee", habitat: "Aquatic Mamals Exhibit", classification: manatee!, zoos: NSSet(array: [lowryParkZoo!]))
33 ]
34
35 // Create -actual- Animal instances and insert them
36 for animal in animals {
37 let newAnimal = NSEntityDescription.insertNewObjectForEntityForName("Animal", inManagedObjectContext: context) as! Animal
38 newAnimal.commonName = animal.commonName
39 newAnimal.habitat = animal.habitat
40 newAnimal.classification = animal.classification
41 newAnimal.zoos = animal.zoos
42 }
43
44 do {
45 try context.save()
46 } catch _ {
47 }
48 }
49
50// ...
51
52}
seedAnimals()
is the most complicated piece of the whole scenario because it depends on entities that have been previously inserted into the data store. An Animal
, which is a member of some Classification
lives in some habitat at a Zoo
. So in order to get a complete Animal
into the data store, we need to have a Classification
and one or more Zoo
to assign it.
I’ve chosen to go ahead and execute fetch requests for the entities I made in previous seed___()
functions. Additionally, rather than deal with NSPredicate
, I just grab all Classifications
and all Zoos
, and use array’s filter
function to get the exact Entity I want.
The last thing I’ll mention / warn against, is that I am using explicitly unwrapped optionals in this function. I’ve gone ahead and “risked it”, trusting that I’m only using this technique during development time to help me see how things will look in my UI. In other words, this isn’t code that will end up in Production. It’s simply meant to help me while I’m developing, so I’ve gone ahead and done things the quick way here.
The rest of the function follows the same patterns that have already been used in this example.
AppDelegate.swift
1func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
2 // Override point for customization after application launch.
3
4 let dataHelper = DataHelper(context: self.managedObjectContext)
5 dataHelper.seedDataStore()
6
7 dataHelper.printAllZoos()
8 dataHelper.printAllClassifications()
9 dataHelper.printAllAnimals()
10
11 return true
12 }
Above is a peek at what my AppDelegate.swift file’s application:didFinishLaunchingWithOptions:
function looks like. Nothing fancy going on here. I’m simply initializing a DataHelper
instance with the NSManagedObjectContext
instance that’s created in this class, and calling the seed and print functions I defined earlier.
Starting fresh every time
I’ve found that sometimes it helps to have a freshly seeded data store every time I the app while I’m in development mode. When I’m testing the UI, I may create new entities during my manual testing, but one of the convenient things about seeding the data store is that I don’t have to. And even if I did, it’s often quite nice to start fresh each run. To do this we’ll dive into some of the boilerplate code that Xcode generates for us when we choose to use Core Data when we create the project. Specifically, we’ll target the persistentStoreCoordinator
closure:
1lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
2 // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
3 // Create the coordinator and store
4 let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
5 let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Zootastic.sqlite")
6
7 do {
8 try NSFileManager.defaultManager().removeItemAtURL(url)
9 } catch _ {
10 }
11
12
13 var failureReason = "There was an error creating or loading the application's saved data."
14 do {
15 try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
16 } catch {
17 // Report any error we got.
18 var dict = [String: AnyObject]()
19 dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
20 dict[NSLocalizedFailureReasonErrorKey] = failureReason
21
22 dict[NSUnderlyingErrorKey] = error as NSError
23 let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
24 // Replace this with code to handle the error appropriately.
25 // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
26 NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
27 abort()
28 }
29
30 return coordinator
31 }()
I’ve highlighted the key line that I added (everything else was already in place, generated for me by Xcode). Adding that line removes the sqlite database. The lines that follow add it back in a fresh state.
Wrapping up
Designing an application’s UI can be difficult without actual data to present. During development, it’s often convenient to seed a Core Data database with sample data so that we can preview how it’ll look in our application. Along with that, it’s nice to start with a fresh copy of the data each time we run the app. We explored how to accomplish this task in Swift!