Categories
CoreData Swift

Storing data with CoreData

CoreData is a framework for managing object graphs and storing them on disk. It is much more than just offering a persistent storage, therefore the rich API it provides, is meant to be used app-wide. In this blog post we’ll look into how to initialise CoreData storage, store some data in it and fetching it.

Core data model

First step is to add a Core Data model which will describe model objects, their relationships and properties. In Xcode we’ll go to File -> New -> File… and then select Data Model.
adding_core_data_model

We are going to continue working on sample app named Planets (from previous post). It needs an entity named Planet which has 3 properties: name, url and position (defines sort order in the app). By default Core Data will autogenerate a model object for our entity Planet we just defined. Therefore all we need to do is creating an entity and adding three properties to the entity.
core_data_model

Initialising CoreData stack

The simplest way for initialising Core Data stack is to use NSPersistentContainer and initialising it with a name of the Core Data model file we just created in the previous step. After creating the container, it needs to be loaded which will read the Core Data model file and sets up a persistent store.

convenience init(name: String)
func loadPersistentStores(completionHandler block: @escaping (NSPersistentStoreDescription, Error?) -> Void)

In the sample app we are going to use subclass of NSPersistentContainer named CoreDataStore which contains extra methods used in a moment.

Storing data

Adding data contains of two steps: making changes in NSManagedObjectContext and then saving it. NSManagedObjectContext is a scratch pad where to make changes in your object graph. All the changes will be stored in memory until save is called.
For adding a new entity we will use a method in our CoreDataStore.

func insertNewEntity(named name: String) -> NSManagedObject {
return NSEntityDescription.insertNewObject(forEntityName: name, into: viewContext)
}

This will add a new empty entity to managed object context. After that, we’ll fill properties with appropriate values and then call save.
func tryStoringDefaultPlanets() {
// dataStore is an instance of NSPersistentContainer subclass CoreDataStore.
guard dataStore.count(for: "Planet") == 0 else { return }
// Insert new entities into managed object context.
let names = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let paths = ["https://en.wikipedia.org/wiki/Mercury_(planet)", "https://en.wikipedia.org/wiki/Venus", "https://en.wikipedia.org/wiki/Earth", "https://en.wikipedia.org/wiki/Mars", "https://en.wikipedia.org/wiki/Jupiter", "https://en.wikipedia.org/wiki/Saturn", "https://en.wikipedia.org/wiki/Uranus", "https://en.wikipedia.org/wiki/Neptune"]
zip(names, paths).enumerated().forEach { (offset, element) in
guard let planet = dataStore.insertNewEntity(named: "Planet") as? Planet else { return }
planet.position = Int64(offset)
planet.name = element.0
planet.url = URL(string: element.1)
}
// Save changes in the managed object context what at the moment contains added Planets.
dataStore.save()
}

Fetching data

In our sample app, there is a simple table view displaying a list of planets. For displaying data efficiently, Core Data has a class NSFetchedResultsController. It uses NSFetchRequest objects for fetching, sorting and filtering results. Our class CoreDataStore has a convenience method for creating fetched results controller for any type of entities and PlanetManager has a getter for returning controller for Planet entities.

final class CoreDataStore: NSPersistentContainer {
func fetchedResultsController(named name: String, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate? = nil, sectionNameKeyPath: String? = nil) -> NSFetchedResultsController<NSFetchRequestResult> {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: name)
if let predicate = predicate {
request.predicate = predicate
}
request.sortDescriptors = sortDescriptors.isEmpty ? nil : sortDescriptors
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: viewContext, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil)
}
}
final class PlanetManager {
lazy var planetsController: NSFetchedResultsController<Planet> = {
let descriptors = [NSSortDescriptor(key: "position", ascending: true)]
return dataStore.fetchedResultsController(named: "Planet", sortDescriptors: descriptors) as! NSFetchedResultsController<Planet>
}()
}

Let’s see how to hook up fetched results controller to table view controller. Before fetched controller is used for fetching data, it needs to perform a fetch. For a simple list view it is pretty straight-forward: getting count and fetching an object for index path.
private func fetchPlanets() {
do {
try planetsController.performFetch()
tableView.reloadData()
}
catch {
NSLog("Fetching failed with error \(error as NSError).")
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return planetsController.sections?[0].numberOfObjects ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PlanetCellIdentifier", for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = planetsController.object(at: indexPath).name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
scenePresenter?.presentDetailedInfo(for: planetsController.object(at: indexPath))
}

Summary

In this blog post we looked into how to initialise Core Data stack with NSPersistentContainer, storing some data in it with NSManagedObjectContext and fetching results with NSFetchedResultsController. It is the most basic usage of CoreData and covers a tiny bit what Core Data can do. Apple has a pretty good documentation what covers much more compared to what was described here (see links to Core Data classes in the previous sections).

Example

Planets (GitHub)

References

CoreData (Apple)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s