CoreData With Concurrency: Swift
1. CREATE & INSERT : Core Data
Using CoreData with background queues is very important to avoid blocking the main one with heavy computations. For this reason, we should use concurrency most of the time.
In this article we are going to see how to save some objects with CoreData in a background queue.
CoreData received a major update with iOS 10. For this reason, in this series, I’ll explain how to achieve our goals in both pre and post iOS 10.
Happy Reading!
Contents
- Getting Started
- iOS 8+
- iOS 10+
Getting Started
Before starting our journey, we have to create a data model—which we will use for all the further examples.
We can use a data model with just an entity
Dog
with a String attribute name
—I know you wanted Cat
. Not today, I prefer dogs iOS 8+
If we want to be compatible with versions older than iOS 10, the first thing which we should do is creating the CoreData stack manually:
As we can see in the example above, we created the main
NSManagedObjectContext
—mainManagedObjectContext
—with the concurrency type mainQueueConcurrencyType
. This means that this context performs any operation in the main queue. We need it to save the data properly in our database. As we can deduce, it’s not the right context for our background tasks.
Fortunately, CoreData allows us to create a context which can work in a background queue. This context will be a child of the main one, since we need the main one to update the data at the end of our computations.
We can create a child context with the following code:
We can notice that the concurrency type is
privateQueueConcurrencyType
. It means that this context works in a background queue.
Now, we are ready to use the child context to keep the persistence of some objects
Dog
with CoreData.
Let’s consider that our application gets a list of
Dog
s from an API endpoint, which returns a JSON like:
In our application, we should send an API request, parse the JSON and keep the persistence of the data with CoreData. The whole process should be done in a background queue to avoid keeping the main queue blocked.
How to send the request and parse the data is up to you. Let’s focus on how to save our data:
For this example we can consider that we parsed the JSON inside an array with the dogs’ name as elements.
This example shows two important functions which allow us to manage the concurrency with different contexts:
perform(_:)
: This is a context method which allows us to execute a closure asynchronously. We can use this method with both main and child context. If we use the main context—mainManagedObjectContext.perform {}
—the closure will be executed in the main queue, otherwise with a child context—like in this example—the closure will be executed in a background queue.performAndWait(_:)
: It’s the same ofperform(_:)
with the only difference that it executes the closure synchronously. It means that it blocks the queue until the closure is executed.
Note
When we save something in a child context, then we must also save in the main one to store properly the data in the database. If we don’t save the data in the main one, then we won’t have the data available on the main context and in all its children but just in the child where we saved the data.
iOS 10+
With iOS 10, Apple introduced an easier way to manage the CoreData stack:
NSPersistentContainer
.NSPersistentContainer
wraps the CoreData stack providing an high-level interface. In this way, we can avoid creating the stack manually but we can just instantiate this object like this:
With this new way, we can create a child context without linking it to the main one, but just calling the method
newBackgroundContext()
. Every contexts are directly linked to the persistent store coordinator. This means that, if we save something in a background context, then we don’t need to save manually in the main one. It will be automatic.
For our example, we don’t need to create a background context manually and call
perform
. We can merely use the method performBackgroundTask
. This method executes a closure in a background queue providing a background context as closure argument.
At this point, we are ready to save our dogs with a persistent container:
As you may have noticed, we can create a new
NSManagedObject
object using its new constructor with the context as parameter:2. READ : Core Data
Contents
NSAsynchronousFetchRequest
- Different Queues Communication
- More…
NSAsynchronousFetchRequest
- Different Queues Communication
- More…
NSAsynchronousFetchRequest
To fetch the data asynchronously in a background queue, CoreData provides the object NSAsynchronousFetchRequest
.
We can create this object passing a NSFetchRequest
argument in its constructor and then executing it thanks to the execute
method of NSManagedObjectContext
.
Moreover, the constructor of NSAsynchronousFetchRequest
has also a closure parameter, which is called when the fetch finishes. This closure has also a fetch result object as parameter–NSAsynchronousFetchResult
–to retrieve the data.
To perform this asynchronous fetch in a background queue, we must call the execute
method using a private context. As we saw in the first part of this series, we have two ways to do it:
iOS 8+
iOS 10+
Once we have our private context, we are ready to perform our fetch:
As we can see in the example above, we can retrieve the result of the fetch inside the completion closure using the property finalResult
of the object passed as parameter of this closure.
Note
Since we are using a NSFetchRequest
to create a NSAsynchronousFetchRequest
, we can also set to fetchRequest
a NSPredicate
to add a specific condition to our query and/or a NSSortDescriptor
to order the query result.
NSAsynchronousFetchRequest
.NSFetchRequest
argument in its constructor and then executing it thanks to the execute
method of NSManagedObjectContext
.NSAsynchronousFetchRequest
has also a closure parameter, which is called when the fetch finishes. This closure has also a fetch result object as parameter–NSAsynchronousFetchResult
–to retrieve the data.execute
method using a private context. As we saw in the first part of this series, we have two ways to do it:finalResult
of the object passed as parameter of this closure.NSFetchRequest
to create a NSAsynchronousFetchRequest
, we can also set to fetchRequest
a NSPredicate
to add a specific condition to our query and/or a NSSortDescriptor
to order the query result.Different Queues Communication
Since we’re fetching using a private context, the completion closure will be executed in a background queue. It means that, if we want to use the data in the main queue—for tasks like updating the UI—we must pass the data from background to the main queue. To achieve it, we can use DispatchQueue.main.async {}
.
When we pass NSManagedObject
between different queues, we should pay attention.
As we can read in the official documentation here:
NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the objectID method on the NSManagedObject instance.
It means that, if we want to use the data in the main queue, we should update our dispatch queue like this:
For the lovers of higher-order functions, we can write the example above in this way:
Remember to use the keyword lazy
. In this way, the chain of higher-order functions will be combined in one function, interating the array just once. If we don’t use lazy
, we would iterate the array twice since we are using two flatMap
.
DispatchQueue.main.async {}
.NSManagedObject
between different queues, we should pay attention.
NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the objectID method on the NSManagedObject instance.
lazy
. In this way, the chain of higher-order functions will be combined in one function, interating the array just once. If we don’t use lazy
, we would iterate the array twice since we are using two flatMap
.More…
NSAsynchronousFetchRequest
allows us also to receive notifications of the fetch progress. It’s an useful feature if we want to show the user some information about the fetching like a progress HUD.
If we want to receive the notification of the progress, we can set a KVO observer like in this example:
Then, we can use the KVO callback to read the new progress data:
If you’re wondering why we have to create a new Progess
object and set it as default one, the answer is that it’s the way to add a child progress—the one of NSPersistentStoreAsynchronousResult
—in the current thread. You can find more details in the official documentation
In this example, we observe completedUnitCount
just for the sake of explanation, we can observer any Progess
property.
NSAsynchronousFetchRequest
allows us also to receive notifications of the fetch progress. It’s an useful feature if we want to show the user some information about the fetching like a progress HUD.Progess
object and set it as default one, the answer is that it’s the way to add a child progress—the one of NSPersistentStoreAsynchronousResult
—in the current thread. You can find more details in the official documentationcompletedUnitCount
just for the sake of explanation, we can observer any Progess
property.3. UPDATE : Core Data
Contents
- Using
NSManagedObject
- Using
NSBatchUpdateRequest
Using NSManagedObject
Let’s consider that in our app we want to allow the user to add some dogs in a list “Favorites”. We would need to add a new boolean attribute
isFavorite
in the Dog
entity:
At this point, when an user wants to add the dog in the “Favorites” list, we have to set the property
isFavorite
to true
in an object Dog
.
Let’s see how to update a
Dog
and save the changes:
iOS 8+
iOS 10+
When we update a
NSManagedObject
object, then we must save these changes manually—like in the examples above—to keep the persistence of these changes.
Using NSBatchUpdateRequest
As we said in “Using
NSManagedObject
“, our App allows the user to add a dog in the list “Favorites”.
At this point, we want to add also the possibility to empty this list, removing all the dogs from this list. It means that we should update all the dogs in this list setting
isFavorite
to false
.
If we want to use the approach of “Using
NSManagedObject
“, we should create a NSManagedObject
per dog to update and then update all of them manually.
Fortunately, CoreData provides a better way to update all the entries which satisfy the criteria of a specific predicate:
NSBatchUpdateRequest
.
Let’s see how to use
NSBatchUpdateRequest
to empty the “Favorites” list:
iOS 8+
iOS 10+
As we can see in these examples, there are four main points to update our data:
- Create the predicate to filter the entity to update.
- Set the new values with the property
propertiesToUpdate
. It’s a dictionary where we have the property names as keys and the new values as dictionary values. In our example, we added just an element, but this dictionary can have several elements, one per property to update. - Execute the request with
resultType
set toupdatedObjectIDsResultType
. - Update the main context.
3. DELETE : Core Data
Contents
Using NSManagedObject
Let’s consider that our App allows the user to delete a specific
Dog
. In this scenario, we would have a NSManagedObject
object to delete.
Once we have a specific
NSManagedObject
, we can delete it very easily. Let's see in the examples below:
iOS 8+
privateManagedObjectContext.perform {
let dog: Dog = // Dog to delete
do {
// Deletes dog
privateManagedObjectContext.delete(dog)
// Saves in private context
try privateManagedObjectContext.save()
mainManagedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try mainManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
iOS 10+
persistentContainer.performBackgroundTask { privateManagedObjectContext in
let dog: Dog = // Dog to delete
do {
// Deletes dog
privateManagedObjectContext.delete(dog)
// Saves in private context
try privateManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
When we delete a
NSManagedObject
object, then we must save these changes manually—like in the examples above—to update our database.
Using NSBatchDeleteRequest
The approach of Using
NSManagedObject
has a problem. If we want to delete all the dogs in our system with a specific name, for example all the Max
dogs, we should create a NSManagedObject
per dog to delete and then delete all of them manually.
Fortunately, Apple introduced, in ios 9, a better way to delete all the entries which satisfy the criteria of a specific predicate:
NSBatchDeleteRequest
.
It’s very similar to
NSBatchUpdateRequest
, which we have seen in the previous part of this series.
For the sake of explanation, let’s consider that we want to delete all the
Dog
s with the name Max
. Let's see how to do it using NSBatchDeleteRequest
:
iOS 9+
privateManagedObjectContext.perform {
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// Creates new batch delete request with a specific request
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
// Asks to return the objectIDs deleted
deleteRequest.resultType = .resultTypeObjectIDs
do {
// Executes batch
let result = try privateManagedObjectContext.execute(deleteRequest) as? NSBatchDeleteResult
// Retrieves the IDs deleted
guard let objectIDs = result?.result as? [NSManagedObjectID] else { return }
// Iterates the object IDs
objectIDs.forEach { objectID in
// Retrieve a `Dog` object queue-safe
let dog = mainManagedObjectContext.object(with: objectID)
// Updates the main context
mainManagedObjectContext.refresh(dog, mergeChanges: false)
}
} catch {
fatalError("Failed to execute request: \(error)")
}
}
iOS 10+
persistentContainer.performBackgroundTask { privateManagedObjectContext in
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// Creates new batch delete request with a specific request
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
// Asks to return the objectIDs deleted
deleteRequest.resultType = .resultTypeObjectIDs
do {
// Executes batch
let result = try privateManagedObjectContext.execute(deleteRequest) as? NSBatchDeleteResult
// Retrieves the IDs deleted
guard let objectIDs = result?.result as? [NSManagedObjectID] else { return }
// Updates the main context
let changes = [NSDeletedObjectsKey: objectIDs]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [mainManagedObjectContext])
} catch {
fatalError("Failed to execute request: \(error)")
}
}
As we saw in these examples, there are three main points to delete our data:
- Create a request to filter the entity to delete.
- Execute the request with
resultType
set toresultTypeObjectIDs
, since we need just the IDs of the objects deleted. - Update the main context.
Note
If we want to support ios 8, then we should fetch and delete all the entries manually like this:
privateManagedObjectContext.perform {
// Creates a request for entity `Dog`
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Dog")
// All the dogs with `name` equal to "Max"
let predicate = NSPredicate(format: "name == %@", "Max")
// Assigns the predicate to the request
request.predicate = predicate
// fetch returns just `objectID`s since we don't need of all the properties
request.includesPropertyValues = false
// Gets fetch result
guard let dogs = try privateManagedObjectContext.fetch(request) as? [NSManagedObject] else { return }
// Deletes dogs manually
for dog in dogs {
privateManagedObjectContext.delete(dog)
}
do {
// Saves in private context
try privateManagedObjectContext.save()
mainManagedObjectContext.performAndWait {
do {
// Saves the changes from the child to the main context to be applied properly
try mainManagedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
We used the fetch property
includesPropertyValues
which lets return just the objectID
s of the fetched elements. To delete an object we don't need all its property, its objectID
is enough.Conclusion
That’s all for our first adventure in the CoreData concurrency world. In the next article, we’ll see how to read the data in a background queue. Stay tuned!
Really, Its so nice explain about Modern Core Data.
ReplyDeleteAttractive portion of content. I just stumbled upon your web site and in accessing capital to claim that I get, in fact enjoyed account your blog posts. Anyway, I'll be subscribing on your augment and even I fulfillment you get entry to constantly rapidly. Have a peek at this website: How To Learn To Password Protect Folder In 1 Hour.
ReplyDeleteThank you for the nice article here. Really nice and keep update to explore more gaming tips and ideas.
ReplyDeleteGame Performance Testing
Game QA
Great Information! Its looking Nice. Safety is most important. Useful for me to develop my knowledge. Thank you!
ReplyDeleteCorporate Training Chennai - Safety Training
Nebosh IGC in Chennai
Nebosh Course in Vizag
Nebosh Courses In Chennai
NEBOSH Course in Pondicherry
Industrial Safety Diploma Course In Chennai
Thanks for sharing such a great blog Keep posting.
ReplyDeletehr contacts database
corporate directory
database providers in mumbai
b2b data companies
relationship intelligence
crm software
sales intelligence
Nice post Excellent Blog , I appreciate your hard work ,it is useful...
ReplyDeleteUI Development Training
UI Development Training in Hyderabad
UI Development Online Training
That's really awesome blog because i found there lot of valuable Information and i am very glad that you share this blog with us.
ReplyDeleteWeblife Infotech
Too good article,keep updating.
ReplyDeleteif you are looking for ios training visit:
ios online training
U3.NET is a cloud studio that enables any business owners to build and manage their own mobile app in an easy and affordable manner, with no technical knowledge required and the ability to update the content and features any time the need arise with U3 you can create a full-fledged mobile application in 3 easy steps (Design, Build, publish) with an array of Value-Adding Features
ReplyDeleteGreat post I would like to thank you for the efforts you have made in writing this interesting and knowledgeable article.
will help you more:
Create Mobile App For iPhone And Android Without Coding
Event apps help minimize fomite transmission by enabling organizers to provide information without the need to distribute physical pamphlets or other materials and while Q&A features eliminate the need to pass a mic around the room. thank you note for hosting and thank you for inviting us to your party
ReplyDeleteشركة تنظيف فلل بالرياض
ReplyDelete