Core Data 101: Saving and Fetching
Core Data is a popular framework that developers can use to manage the persistence of data inside their applications. It is widely used throughout the developer community and is known for being reliable and robust.
Today we will be focusing on a few important aspects of Core Data, namely, how to set up your model schema as well as fetching and saving data to your persistent store.
Getting started
Apple makes it easy to implement Core Data. When creating a new project you will have the option to include Core Data functionality right from the beginning. Simply check the “Use Core Data” checkbox when creating a project and the correct files will be generated and added to your project automatically.
If you have an existing project, you will need to add a Data Model to your app from the File Menu (File > New > File > Data Model). You can find the Data Model file type under the Core Data section.
The Core Data Stack
This tutorial assumes that the Core Data stack (managedObjectModel
, persistentStoreCoordinator
, and managedObjectContext
) has already been setup in advance.
If you haven’t yet setup your stack, be sure to do that by checking this out first before continuing on with this tutorial.
The Core Data stack will look similar to this:
// MARK: - Core Data stack
open lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file.
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1] as NSURL
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application.
let modelURL = Bundle.main.url(forResource: "MyCoreDataProject", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
open lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
// Create the coordinator and store
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("MyCoreDataProject.sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: url, options: nil)
} catch {
// Report any error we got.
abort()
}
return coordinator
}()
open lazy var managedObjectContext: NSManagedObjectContext? = {
// Returns the managed object context for the application
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Setting up the model
In the project navigator, click the .xcdatamodeld file. This file will display the model editor. The model itself allows Core Data to map from records in the persistent store to managed objects in your application.
Note: While we are creating a very simple model today, the model is an important part of implementing Core Data correctly. On more complex models, one with many different entities and attributes, serious thought and consideration should be taken on how to best structure the schema. In other words, don’t rush this step in your own apps.
Our example app is going to keep track of people’s names.
Start by adding a simple Entity to the model. Tap the “Add Entity” button at the bottom of the editor. Then rename the entity that appears. We want to save information relating to people, so let’s call the entity Person.
An entity can contain many different attributes, that is, specific types of data relating to each entity. In our case, we want to save information about a specific Person: their name.
Click your newly created Entity in the model editor, then under the Attributes section, press the plus button to add a new attribute. Name your attribute “name” and set it to be of type String.
Depending on your data storage needs, a model can be set to accept many different types.
- Example A — If you also wanted to capture the user’s age, you could create a new attribute called “age” and set it to be of type Int.
- Example B — If you wanted to capture the time or date the user was created, you could create a new attribute called “date” and set the type to be Date.
Using the correct attribute type in your model allows you to directly modify the object in your code without having to convert it to or from another type.
Example: If you’re storing integers as a String value (you shouldn’t be), to do any type of calculation on these integers will require you to convert them from a string to an integer, do the calculation, then convert them back to a string value for saving. It’s easy to see why choosing (and using) the correct attribute type is important.
Setting up the UI
In our example, we will have two simple buttons. One which for prompting the user to save a new name and one for reading all of the saved named in the database.
Since Core Data is an intermediate to advanced topic for developers, we will leave the simpler creation and connecting of the buttons to you.
Saving Data
To save data to our Core Data model, we will connect one of our newly created buttons to a new function which we named buttonPushPrompt()
.
This function implements a simple alert box which asks the user for their name. Once the user has provided their name and pressed the save button our new saveName()
function will be called. This function creates a new instance of the Person entity with specified name.
Prompting the user:
@IBAction func buttonPushPrompt(_ sender: Any) {
let alertController = UIAlertController(title: "Request",
message: "Please enter your name:",
preferredStyle: .alert)
let actionCancel = UIAlertAction(title: "Cancel", style: .cancel) { (action:UIAlertAction) in
print("You've pressed the cancel button");
}
let actionSave = UIAlertAction(title: "Save", style: .default) { (action:UIAlertAction) in
let textName = alertController.textFields![0] as UITextField;
self.saveName(givenName: textName.text!)
print("The user entered:%@ & %@",textName.text!);
}
alertController.addTextField { (textField) -> Void in
textField.placeholder = "Type your name here"
}
//Add the buttons
alertController.addAction(actionCancel)
alertController.addAction(actionSave)
//Present the alert controller
present(alertController, animated: true, completion:nil)
}
Saving the data:
func saveName(givenName:String) {
let entity = NSEntityDescription.entity(forEntityName: "Person", in:managedObjectContext!)
let item = NSManagedObject(entity: entity!, insertInto:managedObjectContext!)
item.setValue(givenName, forKey: "name")
do {
try managedObjectContext!.save()
} catch _ {
print("Something went wrong.")
}
}
When calling the saveName()
function you will need to provide the name that was inputted by the user.
- Step 1 — Describes which entity we are working with, which in our case is Person. You will remember that this entity was created earlier in our model.
- Step 2 — Create the Person
NSManagedObject
. TheNSManagedObject
will be inserted into our managed object context later when saving. - Step 3 — Now that we have specified our entity and created a new person, we need to save the person’s name. In this case, we use key value coding to set the value for our key, which is specified as “name”.
- Step 4 — At this point, it’s time to save our
managedObjectContext
, which persists the data to the store. If an error should occur, we catch it at this point.
Fetching Data
To fetch data, we will write another function called fetchAndPrintEachPerson()
. This function will request all available data from the Person entity and if objects are found will loop through and print the name value of each person.
func fetchAndPrintEachPerson() {
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
do {
let fetchedResults = try managedObjectContext!.fetch(fetchRequest)
for item in fetchedResults {
print(item.value(forKey: "name")!)
}
} catch let error as NSError {
// something went wrong, print the error.
print(error.description)
}
}
- Step 1 — Create a fetch request. A fetch request is what we use to fetch data from our Core Data store. A fetch request can be customizable to include only the results or attributes that you need. In our case, we want to fetch all objects from the Person entity.
- Step 2 — We now try to fetch data from our Core Data store and store it in our managed object context, whose job it is to create a scratchpad of sorts when dealing with managed objects.
- Step 3 — We have a simple for loop that loops through each result in our fetched items array. At this point, we print out the name of each saved object into the console.
Fetching the first item
You’ve just seen how we can fetch all objects from the Person entity, pretty easy. To grab the first managed object that is returned, you simply request the object at the zero (0) index of the array.
You can see we’ve added a check to ensure that there is at least one item in the array before proceeding. This protects against a crash in the event that no objects exist yet.
func fetchAndPrintEachPerson() {
let fetchRequest = NSFetchRequest<Person>(entityName: "Person")
do {
let fetchedResults = try managedObjectContext!.fetch(fetchRequest)
if fetchedResults.count > 0 {
let personObject: Person = fetchedResults[0]
print("The first person is: \(personObject.name!)")
}
} catch let error as NSError {
// something went wrong, print the error.
print(error.description)
}
}
Conclusion
That’s it for today! Core Data is a really fun and interesting framework to use and we hope that our tutorial on saving and fetching data helps you when building your own apps.
Author's Bio
Ryan Hartman is a senior iOS developer with over 7 years of experience creating awesome apps for iOS and Mac. Located in beautiful Berlin, Germany, he is passionate about helping others learn about programming, UI/UX design, and other technical topics.