Modern, Efficient Core Data
In this tutorial, you’ll learn how to improve your iOS app thanks to efficient Core Data usage with batch insert, persistent history and derived properties. By Andrew Tetlaw.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Modern, Efficient Core Data
30 mins
- Getting Started
- Exploring Fireball Watch
- Examining the Core Data Stack
- Making a Batch Insert Request
- Batch Inserting Fireballs
- Enabling Notifications
- Enabling Persistent History Tracking
- Making a History Request
- Revealing a Conundrum: Big Notifications
- Step 1: Setting a Query Generation
- Step 2: Saving the History Token
- Step 3: Loading the History Token
- Step 4: Setting a Transaction Author
- Step 5: Creating a History Request Predicate
- Step 6: Merging Important Changes
- Adding Derived Attributes
- Where to Go From Here?
Step 6: Merging Important Changes
OK, you've added all of the optimizations you need to make sure that your view context processes changes from only the most relevant transactions. All that's left to do is merge those changes into the view context to update the UI. And that is relatively simple.
Add the following method to your PersistenceController
:
private func mergeChanges(from transactions: [NSPersistentHistoryTransaction]) {
let context = viewContext
// 1
context.perform {
// 2
transactions.forEach { transaction in
// 3
guard let userInfo = transaction.objectIDNotification().userInfo else {
return
}
// 4
NSManagedObjectContext
.mergeChanges(fromRemoteContextSave: userInfo, into: [context])
}
}
}
Here what's going on in the code above:
- You make sure to do the work on the view
context
's queue, usingperform(_:)
. - You loop over each transaction passed to this method.
- Each transaction contains all the details of each change, but you need it in a form you can pass to
mergeChanges(fromRemoteContextSave:into:)
: auserInfo
dictionary.objectIDNotification().userInfo
has just the dictionary you need. - Passing that to
mergeChanges(fromRemoteContextSave:into:)
will bring the view context up to date with the transaction changes.
Remember the query generation you set previously? One of the effects of the mergeChanges(fromRemoteContextSave:into:)
method is to update the context's query generation. Handy!
All that remains is to call your new method. Add the following line to processRemoteStoreChange(_:)
just before the call to print(_:)
(you can also remove that call to print(_:)
if you'd like!):
self.mergeChanges(from: transactions)
The process changes method now filters the transactions and passes only the most relevant ones to the mergeChanges(from:)
method.
Build and run!
Forget the console, check out your app. Refresh twice and you should see nothing happen the second time because no work is required. Then, delete a fireball and then tap the refresh button. You'll see it appear again!
Adding Derived Attributes
You're able to add fireballs to groups, so it'd be nice to show the fireball count in the group list as well.
Derived attributes are a recent addition to Core Data that allow you to create an entity attribute that's computed from child entity data each time the context is saved and stored in the persistent store. This makes it efficient, because you don't need to recompute it each time it's read.
You create a derived attribute in your managed object model. Open FireballWatch.xcdatamodeld and select the FireballGroup entity. Find the Attributes section and click the plus button to add a new attribute. Call it fireballCount
and set the type to Integer 64.
In the Data Model inspector on the right, check the Derived checkbox, which reveals the Derivation field. In this field, type the following:
fireballs.@count
This uses the predicate aggregate function @count
and acts on the existing fireballs
relationship to return the count of how many fireballs are child entities of this group.
Remember to save your managed object model.
All that's left to do is display the count.
Open FireballGroupList.swift, located in the View group, and find the following line:
Text("\(group.name ?? "Untitled")")
Replace it with the following:
HStack {
Text("\(group.name ?? "Untitled")")
Spacer()
Image(systemName: "sun.max.fill")
Text("\(group.fireballCount)")
}
This simply adds an icon and the fireball count to each row. Build and run to see how it displays:
Perfect!
Where to Go From Here?
Well done! Take a step back and admire your work. You should feel proud knowing the world is a little safer from alien invasion because of your app.
You can download the completed project using the Download Materials button at the top or bottom of this tutorial.
If you're looking for a challenge, try adding code to delete the unnecessary transaction history after it's already been processed, to save the history from growing indefinitely. There's a handy method for the job: NSPersistentHistoryChangeRequest.deleteHistoryBefore(_:)
.
If you're looking to learn even more about Core Data, I recommend:
- Making Apps with Core Data from WWDC2019
- Using Core Data With CloudKit from WWDC 2019
- Core Data: Sundries and maxims from WWDC 2020
I hope you enjoyed this Core Data tutorial. If you have any questions or comments, please join the forum discussion below.
Stay vigilant!