Mastering In CoreData (Part 11 Multithreading Concurrency Rules)

Core Data

Supposing you are importing hundreds or thousands of records on the main thread from bundle data into Core Data during the first launch of your application? The consequences could be dramatic. For example, your application could be killed by Apple’s watchdog for taking too long to launch and this significantly slows down the UI performance and might even lead to its complete freezing which is not good user experience. With the concurrency you can do importing task to some other thread which makes your main thread free and user can interact without knowing anything in the background.

Concurrency in Core Data

Concurrency is the ability to work with the data on more than one queue at the same time. The work submitted to these queues is executed on a thread.

Core Data, Multithreading, and the Main Thread

In Core Data, the managed object context which is the heart of the Core Data stack can be used with two concurrency patterns, defined by NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType.

NSMainQueueConcurrencyType

is specifically for use with your application interface and can only be used on the main queue of an application which always run on main thread. As said it can only be used in your application interface (UI) related work. Avoid doing data processing on this. , Like importing data into Core Data from JSON

NSPrivateQueueConcurrencyType

configuration creates its own queue upon initialization and can be used only on that queue. Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the performBlock: and the performBlockAndWait: methods. We will look this in depth when we will be doing coding part.

What managed object context was used in Parts

let managedObjectContext = appDelegate.persistentContainer.viewContext

Previously we are using above code very frequently and we are getting context from the persistentContainer instance property viewContext. As name suggests it’s concurrency type should be NSMainQueueConcurrencyType. When you are using an NSPersistentContainer, the viewContext property is configured as a NSMainQueueConcurrencyType context

PersistentContainer also has two methods performBackgroundTask: and newBackgroundContext the contexts associated with are configured as NSPrivateQueueConcurrencyType.

Figure 1

Core Data is designed to work in a multithreaded environment. However, not every object under the Core Data framework is thread safe. To use Core Data in a multithreaded environment, ensure that:

  1. Managed object contexts are bound to the thread (queue) that they are associated with upon initialization
  2. Managed objects retrieved from a context are bound to the same queue that the context is bound to

(Will see both points through code in this section)

Rule 1

Managed object contexts are bound to the thread (queue) that they are associated with upon initialization.

Go to target → Edit Scheme →Arguments → Add argument “-com.apple.CoreData.ConcurrencyDebug 1” as shown in Figure 2 and 3. We just enabled Core Data Concurrency Debugging

Figure 2
Figure 3

As you can see in Figure 4 we are breaking this rule. We are using managed object context that was created on main thread in the some other thread. We accomplished this by performing number of tasks

  1. First created a context on main thread / main queue (NSMainQueueConcurrencyType) by using Persistent Container viewContext property which creates context on main thread
  2. Created another thread using DispatchQueue method and it will switch the thread
  3. We accessed main queue context on the background thread . At that time debugger will stop the application and telling that you are using context on different thread. If I disable the debugging configuration it will work but there are some scenario in which it will cause a problem. As I said earlier if you follow the rule then threading will be easy for you otherwise you will get random crashes. So to be good citizen you follow the rule “Context created on thread 1 always access this context in that thread

On the Figure 4 we are creating User on main context but the thread we are doing this was not main thread

Figure 4

Solution

Core Data has a features to automatically execute context on the thread it was created but you have to do little work as shown in Figure 5 we add little bit configuration to achieve this

  1. Created context on main thread as we did earlier
  2. Somehow change the thread using DispatchQueue as we did earlier
  3. Used perform(_:) we used to ensure the block operations are executed on the queue specified for the context. Since the context was created on main thread perform(_:) will change the thread to main thread. You can use performAndWait(_:) method also
  4. Now In the perform(_:) block all expression will execute on main thread. Since it first check the context thread and irrespective of the current thread it will change the thread
Figure 5

perform(_:) and performAndWait(_:) ensure the block operations are executed on the queue specified for the context. The perform(_:) method returns immediately and the context executes the block methods on its own thread. With the performAndWait(_:) method, the context still executes the block methods on its own thread, but the method doesn’t return until the block is executed.

Rule of thumb : If you are unsure about the thread. Use perform(_:) or performAndWait(_:) method. Core data automatically take this tension from you since managedObjectcontext is also tracking their thread.

Summary Rule 1

Note: You should never use a main queue context in a background thread. That violates the thread confinement rules. It is a threading violation which means it will work most of the time and then fail in production with a high risk of data corruption

A main queue context should only every be accessed from the main queue (UI queue/thread) or from a performBlock. If you are needing to do a non-UI related task then you should create a private queue context and access it via a performBlock.

To confirm you have your Core Data threading correct you can turn on the -com.apple.CoreData.ConcurrencyDebug 1 runtime setting

Rule 2

Managed objects retrieved from a context are bound to the same queue that the context is bound to

If you will be working on multithreading in core data 100% chance you will be breaking this rule and If you break this rule your app will crash and I came across this crash very frequently. To illustrate this we first need to disable debugging to actually crash the application as shown in Figure 6

Figure 6

Add Exception breakpoint to catch the exception as shown in Figure 7

Figure 7

Download the starter project here and if you you have already delete the application first. We will be working on the User Entity having To Many relationship with the task entity as shown in Figure 8

Figure 8

In the Figure 9 we are breaking rule 2 we created User object on main thread context whereas Task object was created on private context (some other thread) and we are associated that task to user object as shown in Figure 10 app crashed and saying “attempt to create a relationship between two different context”. We accomplished this by performing following tasks

  1. Created a NSMangedObjectContext on main thread using persistent container viewContext property
  2. Created another NSManagedObjectContext on background thread using persistent container newBackgroundContext() method which will give context using concurrency type NSPrivateQueueConcurrencyType.
  3. Created User Object on main thread context
  4. Created task object on private thread context
  5. When we assign task to user object exception raised as shown in FIgure 9 and 10
Figure 9

Continue Program execution you get this crash report “relationship between objects in different context” as shown in Figure 10

Figure 10

As you can see user are trying to access object that is in different context which caused the problem.

One rule of thumb is that object all their relationship properties must create on the same context in which object was created. The question is how can we access other object and we will discussed this in later.

Figure 11

Solution

We will look into the solution after looking into the concurrency strategy and after parent child strategy we will solve this problem using NSManagedObjectID.

Summary

In this part 11 we looked to achieve concurrency without any problem two rules.

What Next?

In the next part we will look Concurrency Problem we have

Useful Links

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/Concurrency.html

https://medium.com/shakuro/introduction-to-ios-concurrency-a5db1cf18fa6

https://cocoacasts.com/swift-and-cocoa-fundamentals-threads-queues-and-concurrency
https://developer.apple.com/documentation/coredata/using_core_data_in_the_background

https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext

https://medium.com/@marcosantadev/core-data-notifications-with-swift-acc8232a674e

https://marcosantadev.com/core-data-notification-swift/

https://www.youtube.com/watch?v=_QolYhiKWvU

Senior iOS Engineer | HungerStation | Delivery Hero