Swift

Thread-safe in Core Data

One important thing to remember when working with Core data is not thread-safe, that means if we access or manipulate a NSManagedObject instance from different threads, it could leads inconsistency result or even crash your app. So it’s very important for you to take care of threading when working with core data. In this article, I’ll show you some essential practices that can help you to derive thread-safe in core data.

Do not use viewContext property

Every NSManagedObjectContext has its own queue. In core data you can get view contexts from NSPersistentContainer, those could be:

  • viewContext which is a context that associated with the main queue.
  • backgroundContext which is the a context that associated with a background queue.

If you use viewContext to manipulate a NSManagedObject there are two cases might occurs :

  1. If you perform the action on a non-main-thread, because it isn’t thread-safe so it may return an inconsistency value or crash your app.
  2. If you perform the action on main thread, it could be fine. But if it’s a heavy action, you will block the client.

So it’s a best practice for you is always using backgroundContext.

Using perform() and performAndWait()

Any actions on NSManagedObject needs to be run on the same queue that associated to the NSManagedObjectContext. Take a look at a method that insert a new object to the core data:

func insertItem(_ item: Item) {
     let entity = ManagedItem(context: backgroundContext)
     entity.id = item.id
     entity.title = item.title
     try? backgroundContext.save()
}

What’s problem with the function above ? That is the function will be run on any threads that it being called. Put another way, the backgroundContext will be run on any threads that the function being called, which is not thread-safe.

So what can we do ? Luckily for us Apple has realized the problem introduced two functions that fix the problem above.

  • perform(_:) : “Asynchronously performs the specified block on the context’s queue” – Apple docs. The function will dispatch all the work inside the closure block to the context’s queue, make the work become thread-safe and returns the control immediately, doesn’t block the current queue.
  • performAndWait(_:) : “Synchronously performs the specified block on the context’s queue.” – Apple docs. The function will dispatch all the work inside the closure block to the context’s queue, make the work become thread-safe but doesn’t return the control until the work done which block the current queue.

We can fix the method above as following:

func insertItem(_ item: Item) {
     let context = self.backgroundContext
     context.perform { // or performAndWait if you want the function perform synchronously
          let entity = ManagedItem(context: context)
          entity.id = item.id
          entity.title = item.title
          try? context.save()
     }
}

Debugging concurrency in the Core data

There’s a key that you can use to debug concurrency issue when using core data which is very useful:

-com.apple.CoreData.ConcurrencyDebug 1

Edit your scheme and add it as an arguments as bellow:

And then if there’s any concurrency issue related to core data at run time the app will crash and point out where the issue is:

Conclusion

In this article I’ve show you best practices I’ve been using in every project since I’ve learned it to and I hope it will help you in your project to avoid unexpected bugs. If you know any else best practice, please share with me I’m eager to learn.

Don’t forget to share if you like it, I’m very appreciate it.

References

  1. Essential Developer
  2. To know more debugging keys

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *