Adding Core Data to our project: NSPersistentContainer
Adding Core Data to our project: NSPersistentContainer êŽë š
A Core Data model defines what your data should look like, but it doesn't actually store the real data anywhere. To make our app work, we need to load that model, create a real working database from it, load that database, then prepare whatâs called a âmanaged object contextâ â an environment where we can create, read, update, and delete Core Data objects entirely in memory, before writing back to the database in one lump.
This all used to be a massive amount of work, to the point where it would put people off Core Data for life. But from iOS 10 onwards, Apple rolled all this work up into a single new class called NSPersistentContainer
. This has removed almost all the tedium from setting up Core Data, and you can now get up and running in just a few lines of code.
So, in this second step we're going to write code to load the model we just defined, load a persistent store where saved objects can be stored, and also create a managed object context where our objects will live while they are active â all using the new NSPersistentContainer
class. Once it finishes its work, weâll have a managed object context ready to work with, and any changes we make to Core Data objects won't be saved until we explicitly request it. It is significantly faster to manipulate objects inside your managed object context as much as you need to before saving rather than saving after every change.
When data is saved, it's nearly always written out to an SQLite database. There are other options, but take my word for it: almost everyone uses SQLite. SQLite is a very small, very fast, and very portable database engine, and what Core Data does is provide a wrapper around it: when you read, write and query a managed object context, Core Data translates that into Structured Query Language (SQL) for SQLite to parse.
If you were wondering, SQL is pronounced Ess Cue Ell, but many people pronounce it "sequel." The pronunciation of SQLite is more varied, but when I met its author I asked him how he pronounces it, so I feel fairly safe that the definitive answer is this: you pronounce SQLite as Ess-Cue-Ell-ite, as if it were a mineral like Kryponite or Carbonite depending on your preferred movie. Unless you plan to get into more advanced usage, you donât need to know anything about SQLite to use Core Data.
To get started, open ViewController.swift
and add an import for Core Data:
import CoreData
Weâre going to create the NSPersistentContainer
as a property, so we can load it once and share it elsewhere in our app. So, add this property now:
var container: NSPersistentContainer!
To set up the basic Core Data system, we need to write code that will do the following:
- Load our data model we just created from the application bundle and create a
NSManagedObjectModel
object from it. - Create an
NSPersistentStoreCoordinator
object, which is responsible for reading from and writing to disk. - Set up a
URL
pointing to the database on disk where our actual saved objects live. This will be an SQLite database namedProject38.sqlite
. - Load that database into the
NSPersistentStoreCoordinator
so it knows where we want it to save. If it doesn't exist, it will be created automatically - Create an
NSManagedObjectContext
and point it at the persistent store coordinator.
Beautifully, brilliantly, all five of those steps are exactly what NSPersistentContainer
does for us. So what used to be 15 to 20 lines of code is now summed up in just six â add this to viewDidLoad()
now:
container = NSPersistentContainer(name: "Project38")
container.loadPersistentStores { storeDescription, error in
if let error = error {
print("Unresolved error \(error)")
}
}
The first line creates the persistent container, and must be given the name of the Core Data model file we created earlier: âProject38â. The next line calls the loadPersistentStores()
method, which loads the saved database if it exists, or creates it otherwise. If any errors come back here youâll know something has gone fatally wrong, but if it succeeds then you can be guaranteed the data has loaded and youâre ready to continue.
Thereâs one small thing we do still need to do ourselves, and thatâs to write a small method to save any changes from memory back to the database on disk. The persistent container gives us a property called viewContext
, which is a managed object context: an environment where we can manipulate Core Data objects entirely in RAM.
Once youâve finished your changes and want to write them permanently â i.e., save them to disk â you need to call the save()
method on the viewContext
property. However, this should only be done if there are any changes since the last save â thereâs no point doing unnecessary work. So, before calling save()
you should read the hasChanges
property. Weâre going to wrap this all up in a single method called saveContext()
â add this new method just after viewDidLoad()
:
func saveContext() {
if container.viewContext.hasChanges {
do {
try container.viewContext.save()
} catch {
print("An error occurred while saving: \(error)")
}
}
}
We'll be calling that whenever we've made changes that should be saved to disk.
At this point, our app has a working data model as well as code to load it into a managed object context for reading and writing. That means step two is done and we're on to step three: creating objects inside Core Data and fetching data from GitHub.