Fetching Remote Data With Core Data Background Context in iOS App
Core Data is an object graph and persistence framework provided by Apple for developing iOS Apps. It handles object life cycle, object graph management, and persistence. It supports many features for handling model layer inside an app such as:
- Relationship management among objects.
- Change tracking with Undo Manager
- Lazy loading for objects and properties
- Grouping, filtering, querying using NSPredicate
- Schema migration
- Uses SQLite as one of its option for backing store.
With so many advanced features provided automatically out of the box by Core Data, it has steep learning curve for developer to learn and use for the first time. Before iOS 10, to setup Core Data in our application there are many configuration and boilerplate code we need to perform to build a Core Data Stack. Luckily in iOS 10, Apple introduced NSPersistentContainer that we can use to initialize all the stack and get the NSManagedObject context with very little code.
In this article, we will build a simple demo app that fetch list of films from the remote Star Wars API and sync the data inside the Core Data store using background queue naively without synchronization strategy. What we will build:
- Managed Object Model Schema and Film Entity.
- Managed Object for Film Entity.
- CoreDataStack: Responsible for building the NSPersistentContainer using the schema.
- ApiRepository: A class responsible for fetching list of films data from StarWars API using URL Session Data Task.
- DataProvider: A class that provide interface to fetch list of film from data repository and sync it to the Core Data store using NSManagedObjectContext in background thread.
- FilmsViewController: View Controller that communicates with data provider and uses NSFetchedResultsController to fetch and observe change from Core Data View Context, then display list of films in a UITableView.
You can checkout the complete source code for this app in the project GitHub repository.
iOS App that Fetch Star Wars API Using Core Data Background Context - alfianlosari/CoreData-Fetch-API-Background
You can also checkout and try the StarWars API by clicking the link to the website below.
SWAPI - The Star Wars API
The Star Wars API, or "swapi" (Swah-pee) is the world's first quantified and programmatically-accessible data source…
Managed Object Model Schema and Film Entity
The first step we will perform is to create the Managed Object Model Schema that contains a Film Entity. Create New File from Xcode and select Data Model from Core Data Template. Name the file as StarWars, it will be saved with the .xcdatamodeld as the filename extension.
Click on the Data Model file we just created, Xcode will open Data Model Editor where we can add Entity to the Managed Object Model Schema. Click Add Entity and Set the name of the new Entity as Film. Make sure to set the codegen is set to Manual/None so Xcode does not automatically generate the Model class. Then add all the attributes with the type like the image below:
Create Managed Object for Film Entity
After we have created the schema with Film Entity, we need to create new file for Film class with NSManagedObject as the superclass. This class will be used when we insert Film Entity into NSManagedObjectContext. Inside we declare all the properties related to the entity with associated type, the property also need to be declared with @NSManaged keyword for the compiler to understand that this property will use Core Data at its backing store. We need to use NSNumber for primitive type like Int, Double, or Float to store the value in a ManagedObject. We also create a simple function that maps a JSON Dictionary property and assign it to the properties of Film Managed Object.
Setup Core Data Stack
To setup our Core Data Stack that uses the Managed Object Model Schema we have created, create a new file called CoreDataStack. It will be a Singleton class that exposes NSPersistentContainer public variable. To initialize the container, we just pass the filename of the Managed Object Model schema which is StarWars. We also set the view NSManagedObjectContext of the container to automatically merge changes from parent, so when we use the background context to save the data, the changes will also be propagated to the View Context.
Next, create a new file with the name of ApiRepository. This Singleton class acts as a Networking Coordinator that connects to the SWAPI to fetch list of films from the network. It provides public method to get films with completion closure as the parameter. The closure will be invoked with either of Array JSON Dictionary or an error in case of an error occurs when fetching or parsing the JSON data from the response.
The next file wee need to create is the DataProvider class. This class responsibility is to act as Sync Coordinator to fetch data using the ApiRepository and store the data to the Core Data Store. It accepts the repository and NSPersistent container as the initializer parameters and store it inside the instance variable. It also exposes a public variable for the View NSManagedObjectContext that uses the NSPersistetContainer View Context.
The fetchFilms function can be used by the consumer of the class to trigger the synchronization to the API repository to get the films. After the data has been received, we initialize a Background NSManagedObjectContext using the NSPersistentContainer newBackgroundContext method.
We use the NSManagedObjectContext synchronous performAndWait function to perform our data synchronization. The synchronization just perform a naive synchronization technique by:
- Find all the films that match all the episode id we retrieve from the network inside our current Core Data Store using NSPredicate. To be efficient, we are not retrieving the actual object only the NSManagedObjectID.
- Delete the films found in our store using NSBatchDeleteRequest.
- Insert all the films using the response from the repository.
- Update the property of the films using the JSON Dictionary.
- Save the result to the Core Data Store.
- Changes will be automatically merged to the View Context
Integration with View Controller (UI)
Inside the Main.storyboard drag a UITableViewController and create one prototype table view cell with “Cell” as the identifier and Subtitle as the style. Make sure to embed it inside UINavigationController and set it as the initial view controller.
Create a new File with the name of FilmListViewController. The FilmListViewController inherits from UITableViewController as the superclass. Inside there are 2 instance properties we need to declare:
- DataProvider: The DataProvider class that we will use to trigger the synchronization of the films. It will be injected from the AppDelegate when the application launch.
- NSFetchedResultsController: NSFetchedResultsController is Apple Core Data class that acts a controller that you use to manage the results of a Core Data fetch request and display data to the user. It also provides delegation for the delegate to receive and react to the changes when the related entity in the store changes. In our case we use NSFetchRequest to fetch the Film entity, then tells it sort the result by episodeId in ascending order. We initialize the NSFetchedResultController with the FetchRequest and the DataProvider View Context. The FilmListViewController will also be assigned as the delegate so it can react and update the TableView when the underlying data changes.
The TableViewDataSource methods will ask the NSFetchedResultsController for its section, number of rows in a section, and the actual data for the table view cell at given IndexPath. We set the text label and detail text label of the cell with the title of the film and director of the film from the Film object.
For the NSFetchedResultController delegate we override the controllerDidChangeObject to just reload the TableView naively for the sake of this example. You can perform fine grained TableView update with animation here if you want using the indexPaths given.
At last, make sure to set the class of the UITableViewController inside the storyboard to use the FilmListViewController class. Build and run the project to test.
Core Data is a very powerful framework that we can use when we want to build an app that want to synchronize data from cloud and has the feature to work offline. There are other solution for other persistence framework that has relational capabilities such as Realm Database that we can use as another options.