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 NSManagedObjectContextmainManagedObjectContext—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 Dogs 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 of perform(_:) 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

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.

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.

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.

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 isFavoriteto 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:
  1. Create the predicate to filter the entity to update.
  2. 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.
  3. Execute the request with resultType set to updatedObjectIDsResultType.
  4. 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 Dogs 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:

  1. Create a request to filter the entity to delete.
  2. Execute the request with resultType set to resultTypeObjectIDs, since we need just the IDs of the objects deleted.
  3. 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 objectIDs 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!

Comments

  1. Really, Its so nice explain about Modern Core Data.

    ReplyDelete
  2. Attractive 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.

    ReplyDelete
  3. Thank you for the nice article here. Really nice and keep update to explore more gaming tips and ideas.

    Game Performance Testing

    Game QA

    ReplyDelete
  4. 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.
    Weblife Infotech

    ReplyDelete
  5. Too good article,keep updating.

    if you are looking for ios training visit:

    ios online training

    ReplyDelete
  6. 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
    Great 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

    ReplyDelete
  7. 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

Post a Comment

Popular posts from this blog

iOS Architecture

Performance Tips for IOS Application

setNeedsLayout vs layoutIfNeeded Explained