What's New Library Search Sign In
Fetching Records With Core Data
Fetching Records With Core Data: Type
Methods
by Bart Jacobs in Core Data
Swift 4 Xcode 9 iOS 11
Fetching Records With Core Data
1 Fetching Records With Core Data: Type Methods
2 Type Methods 08:43
Featured
Samsara 6 Is Here!
Samsara is a minimalist timer for yoga, meditation, and
mindfulness. It sports a simple design that lets you focus on what
matters, your practice. Over the past ten years, it has become the
companion of thousands of people around the world. Download
Samsara for free from the App Store.
Developers often complain that Core Data has an arcane syntax and a complicated
API. "It's tedious to work with Core Data." seems to be the general consensus. It's true
that Core Data used to be difficult to use and the framework's syntax wasn't as
elegant as it could be. That's something of the past, though. The more Core Data
matures, the more I enjoy and appreciate the framework.
First impressions are difficult to change and it's therefore unsurprising that
developers often fall back on third party libraries. Using a third party library to
interact with a first party framework isn't something I recommend.
Many of us find fetching records from a persistent store to be clunky and tedious. Is
that true? In this series, I'd like to show you how easy and elegant fetching records
from a persistent store can be.
We start with a simple example every developer familiar with Core Data
understands. Along the way, we add more complexity by introducing flexibility and
dynamism. We also leverage generics to make sure we don't unnecessarily repeat
ourselves. Core Data and generics play very well together. Let's start with a simple
data model.
Setting the Stage
Setting Up the Project
Launch Xcode and create a project based on the Single View App template.
Let's assume we're creating an application that tracks the user's workouts. Name the
project Workouts and, to save a bit of time, check the Use Core Data checkbox at
the bottom.
Chooseoptionsforyournewproject:
ProductName: Workouts
Team: BartJacobs
OrganizationName: Cocoacasts
OrganizationIdentifier: com.cocoacasts
BundleIdentifier:com.cocoacasts.Workouts
Language:Swift
/UseCoreData
•IncludeUnitTests
IncludeUlTests
Cancel Previous Next
Populating the Data Model
The data model isn't too complicated. This is what it looks like. We define three
entities, Workout, Exercise, and Session.
As you can see, a workout has a name and it's linked to zero or more exercises. An
exercise can belong to only one workout. Completed workouts are saved as sessions
hence the Session entity. A session stores the duration of the workout and it also
keeps a reference to the workout.
Every entity I create defines three default attributes:
uuid of type String
createdAt of type Date
updatedAt of type Date
If your application's deployment target is iOS 11 or higher, you can use the brand new
UUID attribute type for the uuid attribute.
This is a summary of the entities of the data model.
Workout
Attributes - name of type String - uuid of type String - createdAt of type Date -
updatedAt of type Date
Relationships - sessions with Session as its destination - exercises with Exercise as
its destination
Exercise
Attributes - name of type String - uuid of type String - createdAt of type Date -
updatedAt of type Date - duration of type Double - order of type Integer 16
Relationships - workout with Workout as its destination
Session
ENTITIES VAttributes
E)Exercise
ESession Entity Attribute^ Type
E)Workout Session DcreatedAt Date
Session DupdatedAt Date
FETCHREQUESTS Session Suuid String
CONFIGURATIONS +-
©Default
Relationships
Entity Relationship^ Destination Inverse
Session ®workout Workout ^sessions^
+-
FetchedProperties
Entity FetchedPropertya Predicate
+-
Attributes - uuid of type String - createdAt of type Date - updatedAt of type Date
Relationships - workout with Workout as its destination
Xcode automatically creates a class definition for us. Why is that? Open the data
model by selecting it in the Project Navigator. Select the Workout entity and open
the Data Model Inspector on the right. Notice that Codegen in the Class section is
set to Class Definition. This means that Xcode generates a class definition for us. It's
the default setting in Xcode 9 at the time of writing.
First Things First
Fetching data from a persistent store isn't difficult. Let me show you what it looks
like. Before we start, though, we need to inject the managed object context of the
persistent container in the root view controller of the window. Euh ... what? Don't
worry. It's easy. This is what that looks like.
func application
application((_ application
application:: UIApplication
UIApplication,, didFinishLaunchingWithOptions launchOptions
launchOptions:: [ UIApplicationLaunchOptionsKey
// Load Storybaord
let storyboard = UIStoryboard
UIStoryboard((name
name:: "Main"
"Main",, bundle
bundle:: Bundle
Bundle..main
main))
// Instantiate Initial View Controller
guard let viewController = storyboard
storyboard..instantiateInitialViewController
instantiateInitialViewController(() as
as?? ViewController
fatalError
fatalError(()
}
// Configure View Controller
viewController
viewController..managedObjectContext = persistentContainer
persistentContainer..viewContext
// Configure Window
window
window??. rootViewController = viewController
return true
}
In the application(_:didFinishLaunchingWithOptions:) method of the AppDelegate class, we
instantiate the initial view controller of the main storyboard, an instance of the
ViewController class. We inject the managed object context of the persistent
container by setting the managedObjectContext property of the view controller. This only
works if we set the ViewController instance as the root view controller of the
application's window.
Before we can build and run the application, we need to declare a variable property,
managedObjectContext , of type NSManagedObjectContext! in the ViewController class. To make
sure we didn't make any mistakes, add a print statement in the view controller's
viewDidLoad() method and run the application.
import UIKit
import CoreData
class ViewController
ViewController:: UIViewController {
// MARK: - Properties
var managedObjectContext
managedObjectContext:: NSManagedObjectContext
NSManagedObjectContext!!
// MARK: - View Life Cycle
override func viewDidLoad
viewDidLoad(
() {
super
super..viewDidLoad
viewDidLoad(()
print
print((managedObjectContext
managedObjectContext))
}
This is a quick and dirty setup, but it's fine for what I'm about to show you. It's time to
fetch records.
Fetching Records
The persistent store is empty at the moment, but that isn't a problem for this
tutorial. The goal is to show you how to easily and elegantly fetch records from a
persistent store. Let's start with a simple fetch request.
In the viewDidLoad() method of the ViewController class, we create a fetch request by
invoking the fetchRequest() class method of the Workout class. Remember that Xcode
has generated the Workout class for us, a NSManagedObject subclass. The fetchRequest()
class method returns a NSFetchRequest<Workout> instance.
// MARK: - View Life Cycle
override func viewDidLoad
viewDidLoad(
() {
super
super..viewDidLoad
viewDidLoad(()
// Create Fetch Request
let fetchRequest
fetchRequest:: NSFetchRequest
NSFetchRequest<<Workout
Workout>> = Workout
Workout..fetchRequest
fetchRequest(()
do {
// Peform Fetch Request
let workouts = try managedObjectContext
managedObjectContext..fetch
fetch((fetchRequest
fetchRequest))
print
print((workouts
workouts))
} catch {
print
print(("Unable to Fetch Workouts, (
(\(
\(error
error)))"
)"))
}
}
We execute the fetch request by passing it to the fetch(_:) method of the
NSManagedObjectContext instance. The fetch(_:) method is throwing, which means we
need to wrap it in a do-catch statement and attach the try keyword to the method
invocation.
Because the persistent store is empty at the moment, the result printed to Xcode's
console is an empty array.
We've successfully executed a fetch request. However, by taking this approach to
interact with Core Data, the code you write is verbose and hard to test. The approach
I recommend is simpler and easier to test.
Type Methods
We start by creating a Swift file, Workout.swift, and define an extension for the
Workout class. Add an import statement for the Core Data framework at the top.
import CoreData
extension Workout {
I want to encapsulate fetching of Workout records in the Workout class. Let's start by
defining a class method, findAll(in:) that accepts a managed object context as its
only argument. The managed object context we pass to findAll(in:) is the one
executing the fetch request.
class func findAll
findAll((in managedObjectContext
managedObjectContext:: NSManagedObjectContext
NSManagedObjectContext)) - > [ Workout
Workout]] {
The implementation should look familiar. We create a fetch request and hand it to
the managed object context we pass to findAll(in:) .
class func findAll
findAll((in managedObjectContext
managedObjectContext:
: NSManagedObjectContext
NSManagedObjectContext)) - > [ Workout
Workout]] {
// Helpers
var workouts
workouts:: [ Workout
Workout]] = []
// Create Fetch Request
let fetchRequest
fetchRequest:: NSFetchRequest
NSFetchRequest<<Workout
Workout>> = Workout
Workout..fetchRequest
fetchRequest(()
do {
// Perform Fetch Request
workouts = try managedObjectContext
managedObjectContext..fetch
fetch((fetchRequest
fetchRequest))
} catch {
print
print(("Unable to Fetch Workouts, (
(\(
\(error
error)))"
)"))
}
return workouts
}
That's a good start, but there's room for improvement. Instead of handling the error
in the findAll(in:) method, it's more useful to propagate it to the method's call site.
This is easy to do by turning findAll(in:) into a throwing class method and omitting
the do-catch statement.
class func findAll
findAll((in managedObjectContext
managedObjectContext:
: NSManagedObjectContext
NSManagedObjectContext)) throws - > [ Workout
Workout]] {
// Helpers
var workouts
workouts:: [ Workout
Workout]] = []
// Create Fetch Request
let fetchRequest
fetchRequest:: NSFetchRequest
NSFetchRequest<<Workout
Workout>> = Workout
Workout..fetchRequest
fetchRequest(()
// Perform Fetch Request
workouts = try managedObjectContext
managedObjectContext..fetch
fetch((fetchRequest
fetchRequest))
return workouts
}
If this isn't what you want, then you can implement a variation that silences any
errors that are thrown. It looks something like this. Take a moment to understand
what's going on.
class func findAll
findAll((in managedObjectContext
managedObjectContext:: NSManagedObjectContext
NSManagedObjectContext)) - > [ Workout
Workout]] {
// Create Fetch Request
let fetchRequest
fetchRequest:: NSFetchRequest
NSFetchRequest<<Workout
Workout>> = Workout
Workout..fetchRequest
fetchRequest(()
return ( try
try?? managedObjectContext
managedObjectContext..fetch
fetch((fetchRequest
fetchRequest))) ? ? [ ]
}
If we use the try? keyword and an error is thrown, the error is handled by turning
the result into an optional value. This means that there's no need to wrap the
throwing method call in a do-catch statement. But notice that we don't return an
optional value as the result. If executing the fetch request fails for some reason, we
return an empty array.
I don't recommend ignoring errors and I therefore prefer the first implementation of
the findAll(in:) class method.
We can now update the implementation of the viewDidLoad() method of the
ViewController class. The result is cleaner and less verbose.
override func viewDidLoad
viewDidLoad(
() {
super
super..viewDidLoad
viewDidLoad(()
do {
// Fetch Workouts
let workouts = try Workout
Workout..findAll
findAll((in
in:: managedObjectContext
managedObjectContext))
print
print((workouts
workouts))
} catch {
print
print(("Unable to Fetch Workouts, (
(\(
\(error
error)))"
)"))
}
}
If we aren't interested in any errors that are thrown, we can still fall back to the try?
keyword. That's the benefit of choosing for a throwing method.
override func viewDidLoad
viewDidLoad(
() {
super
super..viewDidLoad
viewDidLoad(()
// Fetch Workouts
if let workouts = try
try?
? Workout
Workout..findAll
findAll((in
in:: managedObjectContext
managedObjectContext)) {
print
print((workouts
workouts))
} else {
print
print(("Unable to Fetch Workouts"
Workouts"))
}
}
By opting for the try? keyword, we don't ignore the error. It simply means that
we're not interested in the reason of the failed fetch request.
I frequently see developers print or log errors. I assume it gives them the feeling that
they're handling the error in some way. While this is helpful during development,
when things hit the fan an error is only useful if you take some action in response to
the error.
Adding Flexibility
I hope this tutorial has shown you that it's very easy to factor out a fetch request into
a class method. This makes it easier to test your code and it cleans up the call site
substantially.
In the next tutorial, we take it to the next level by adding more flexibility and
dynamism. We often need the ability to find specific records and it's also essential to
have the option to sort the results of the fetch request. This is something Core Data
needs to handle for us.
You might also like ...
Hard-Coding Seed Data
Type Methods
Asynchronous Fetching With Core Data And Operations
Versioning the Data Model
Welcome to Core Data Fundamentals
Next Episode "Type Methods"
Solutions Resources About Legal
Cocoacasts Plus Books Cocoacasts Privacy Policy
Mentorship Swift Patterns Testimonials Terms and Conditions
GitHub
Twitter
© Code Foundry 2016-2023