When you create a new project in Xcode, Xcode asks whether you want to add Core Data to your application. If you check this checkbox, Xcode will automatically generate some boilerplate code that sets up the Core Data stack. For practicing purposes, MustC was set up without Core Data so you'll have to add this to the project yourself. Start by opening AppDelegate.swift and add the following import statement:
import CoreData
Next, add the following lazy variable to the implementation of AppDelegate:
private lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "MustC") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error { fatalError("Unresolved error (error), (error.userInfo)") } }) return container }()
The preceding code snippet creates an instance of NSPersistentContainer. The persistent container is a container for the persistent store coordinator, persistent store, and managed object context. This single object manages different parts of the Core Data stack, and it ensures that everything is set up and managed correctly.
If you let Xcode generate the Core Data code for your app, it adds a similar property to create an NSPersistentContainer. Xcode also adds a method called saveContext() to AppDelegate. This method is used in applicationWillTerminate(_:) to perform a last-minute save of changes and updates when the application is about to terminate. Since you're setting up Core Data manually, this behavior isn't added by Xcode so it must be added by you manually.
Instead of placing the saveContext() method in AppDelegate, you will add this method as an extension to NSPersistentContainer. This makes it easier for other parts of your code to use this method, without having to rely on AppDelegate.
Create a new folder in the Project Navigator and name it Extensions. Also, create a new Swift file and name it NSPersistentContainer.swift. Add the following implementation to this file:
import CoreData extension NSPersistentContainer { func saveContextIfNeeded() { if viewContext.hasChanges { do { try viewContext.save() } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } }
This code adds a new method to NSPersistentContainer instances by extending it. This is really convenient because it decouples the save method from AppDelegate entirely. This is much nicer than the default save mechanism provided for Core Data apps by Xcode. Add the following implementation of applicationWillTerminate(_:) to AppDelegate to save the context right before the app terminates:
func applicationWillTerminate(_ application: UIApplication) { persistentContainer.saveContextIfNeeded() }
Now, whenever the application terminates, the persistent store will check whether there are any changes to the managed object context that the viewContext property points to. If there are any changes, an attempt to save them is made. If this attempt fails, the app will crash with a fatalError. In your own app, you might want to handle this scenario a bit more gracefully. It could very well be that failing to save data before the app terminates is no reason to crash in your app. You can modify the error-handling implementation of saveContextIfNeeded() if you think a different behavior is more appropriate for your app.
Now that you have the Core Data stack set up, you need a way to provide this stack to the view controllers in the app. A common technique to achieve this is called dependency injection. In this case, dependency injection means that AppDelegate will pass the persistent container to FamilyMemberViewController, which is the first view controller in the app. It then becomes the job of FamilyMemberViewController to pass the persistent container to the next view controller that depends on it, and so forth.
In order to inject the persistent container, you need to add a property to FamilyMembersViewController that holds the persistent container. Don't forget to import Core Data at the top of the file and add the following code:
var persistentContainer: NSPersistentContainer!
Now, in AppDelegate, modify the application(_:didFinishLaunchingWithOptions:) method as follows:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { if let navVC = window?.rootViewController as? UINavigationController, let initialVC = navVC.viewControllers[0] as? FamilyMembersViewController { initialVC.persistentContainer = persistentContainer } return true }
Even though this code does exactly what it should, you can make one major improvement. You know that there might be more view controllers that depend on a persistent container. You also learned that checking whether something is a certain type is something you should generally avoid. As an exercise, attempt to improve the code by adding a protocol called PersistenContainerRequiring. This protocol should add a requirement for an implicitly-unwrapped persistentContainer property. Make sure that FamilyMembersViewController conforms to this protocol and fix the implementation of application(_:didFinishLaunchingWithOptions:) as well so it uses your new protocol.
You have just put down the foundation that is required to use Core Data in your app. Before you can use Core Data and store data in it, you must define what data you would like to save by creating your data model. Let's go over how to do this next.