In the previous tutorial, you learned about the Core Data stack and the classes involved. In this tutorial, we take a close look at Xcode's template for Core Data applications.
Setting Up the Project
Open Xcode and select New > Project... from the File menu. Choose the Single View Application template from the iOS > Application section and click Next.
Name the application Core Data, enter an organization name and identifier, and set Language to Swift. Check the Use Core Data checkbox to make sure Xcode adds the necessary files and code for working with Core Data.
Exploring Files and Folders
The project includes a file that may be new to you, Core_Data.xcdatamodeld. This is the data model I talked about in the previous tutorial. Select the file to take a quick peek at its contents.
Because the data model doesn't contain any entities yet, there isn't much to see. We take a closer look at the data model and the data model editor in Mastering Core Data With Swift 3.
Exploring the Application Delegate
More interesting is the implementation of the AppDelegate
class. Open AppDelegate.swift and take a moment to see what Xcode has added for us when we checked the Use Core Data checkbox during the project setup. The first detail worth noting is the import statement for the Core Data framework at the top.
import UIKit
import CoreData
The other interesting bits are the lazy stored property, persistentContainer
, of type NSPersistentContainer
and the saveContext()
helper method.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded 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.
*/
let container = NSPersistentContainer(name: "Core_Data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
Xcode's Core Data template received a dramatic makeover with the release of the NSPersistentContainer
class. This class was introduced alongside iOS 10, tvOS 10, macOS 10.12, and watchOS 3. While it greatly simplifies setting up Core Data, it isn't ideal for learning the framework. Why is that?
The NSPersistentContainer
class encapsulates the Core Data stack. It is in charge of setting up the Core Data stack and managing the building blocks we discussed in the previous tutorial. This means that we don't have any insight into the inner workings of the NSPersistentContainer
class, which makes it difficult to illustrate what we learned about the Core Data stack in the previous tutorial.
Fortunately, the NSPersistentContainer
class provides access to the managed object model, the managed object context, and the persistent store coordinator. In Mastering Core Data With Swift 3, we take a different approach by building a Core Data stack from scratch. This teaches you essential insights into the inner workings of the Core Data stack, the heart of every Core Data application. If you are serious about learning and using Core Data, then you should take Mastering Core Data With Swift 3. It also discuss the NSPersistentContainer
class in more detail.
This is what the lazy property looks like. Comments are omitted for clarity.
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Core_Data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
The NSPersistentContainer
instance is initialized by invoking a convenience initializer, init(name:)
, passing in the name of the data model, Core_Data. Notice that there is no explicit instantiation of NSManagedObjectModel
, NSPersistentStoreCoordinator
, or NSManagedObjectContext
. This is abstracted away by the NSPersistentContainer
class.
let container = NSPersistentContainer(name: "Core_Data")
A Core Data stack without a persistent store isn't terribly useful. After initializing the persistent container, the persistent store is loaded by invoking the loadPersistentStores(completionHandler:)
method. It accepts one parameter, a closure, which is invoked when loading of the persistent store is completed, successfully or unsuccessfully. The closure accepts two parameters, an instance of the NSPersistentStoreDescription
class and an optional Error
object.
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
The NSPersistentStoreDescription
class is another new addition to the Core Data framework. It encapsulates the information and configuration to add a persistent store to the persistent store coordinator. As the name of the class implies, it describes a persistent store.
What Is a Persistent Container?
As I already mentioned, the NSPersistentContainer
class encapsulates the Core Data stack and it is available as of iOS 10, tvOS 10, macOS 10.12, and watchOS 3. It makes setting up a Core Data stack easier than ever, providing developers with a simple setup with some flexibility.
After creating an instance of the NSPersistentContainer
class, you can easily access the managed object model and the persistent store coordinator through the managedObjectModel
and persistentStoreCoordinator
properties.
persistentContainer.managedObjectModel
persistentContainer.persistentStoreCoordinator
The class also provides easy access to a viewContext
property, an instance of the NSManagedObjectContext
class that operates on the main queue. As the name of the property implies, this managed object context is designed to be used in combination with the application's user interface. Main queue? User interface? Don't worry about this for now. We discuss this in more detail in Mastering Core Data With Swift 3.
persistentContainer.viewContext
There are several other neat features, such as the ability to create background managed object contexts and specifying the directory in which the persistent store lives by subclassing NSPersistentContainer
. These are more advanced features.
Saving Changes
The application template also includes a helper method to save the changes of the managed object context. In this method, we access the managed object context of the persistent container through the viewContext
property.
We check if the managed object context has any changes by inspecting the value of the hasChanges
property. To save the changes of the managed object context, we invoke save()
on the managed object context. Because this method is throwing, we wrap it in a do-catch
statement.
func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
Refactoring the Core Data Stack
I always try to keep the implementation of the application delegate short and simple. Setting up the Core Data stack is something we could and should move to a dedicated class. This makes the implementation of the AppDelegate
class shorter and less cluttered. This is something we discuss in detail in Mastering Core Data With Swift.
Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files for this article from GitHub.
Now that you know what Core Data is and how the Core Data stack is set up, it's time to write some code. If you're serious about Core Data, check out Mastering Core Data With Swift. We build an application that is powered by Core Data and you learn everything you need to know to use Core Data in your own projects.