Core Data is controversial to many developers. Some love it while others think it is too difficult or that it contains too many bugs. Personally I enjoy using Core Data. Out of the box it provides a lot of functionality needed for managing the model layer of most applications and because it is supported by Apple it saves us from adding another third part dependency to our projects.
However I do think Core Data has a high learning curve. This weeks blogpost is about one of the things of Core Data that took me a while to understand, the perform(_:)
and performAndWait(_:)
api. Let’s dive in.
Core Data in a nutshell
In Core Data all model objects are subclasses of NSManagedObject
. You typically access those objects from a NSManagedObjectContext
, which is like a scratchpad that allows you to fetch, change, add and delete managed objects and save these changes to a persistent store. The persistent store is like a repository in which managed objects get stored. Usually the persistent store is a SQLite database.
NSManagedObjectContext Concurrency policies
A managed object context is tied to a serial queue to ensure that all operations on the context are performed in order so that the data stays consistent. Therefore the most important thing to remember is that you should always access the context and its managed objects from its own queue. Which type of queue is used by the context depends on its concurrencyType
.
A context with the NSPrivateQueueConcurrencyType
has its own background queue. This type of context, usually called a background or private context, is great for executing work that can block the main queue for example importing a large JSON response into the persistent store.
A context with the NSMainQueueConcurrencyType
does not have its own queue but instead is connected to your applications main queue. This type of context, NSPersistentContainer
calls it a view context, is typically used for the user interface of your application.
That is all very straightforward right? But the queue used by the context is private so how can we make sure we only access the context from its own queue? This is where the perform(_:)
and performAndWait(_:)
methods comes in. Work that is wrapped inside these methods is executed on the contexts queue. On a background queue you always need to wrap your work into one of these methods. For view contexts this is a bit different, because they are typically used for the user interface most of the time you can access the context directly since the UI runs on the main queue. However if you want to access a main context or its managed objects on a background queue you do need to use the perform(_:)
api to make sure the work is executed on the main queue.
perform(_:)
The perform(_:)
method is asynchronous which means it will return immediately and does not block the calling thread. The block submitted through this method will be executed on the queue’s context in FIFO order.
One of the advantages of using Core Data is that it provides you with change tracking, which is great for writing reactive user interfaces. The perform(_:)
method treats the block you have submitted as a user event, which means that at the end of every perform block it will make sure that all changes are processed and relevant notifications are sent. NSFetchedResultsController
for example automatically updates your tableview in response to those notifications.
Lastly, every block submitted through the perform(_:)
method gets wrapped in a autorelease pool.
performAndWait(_:)
The performAndWait(_:)
method is synchronous which means it will return once the block submitted to the queue has been executed. Thus it will block the calling thread. A block submitted through the performAndWait(_:)
is not treated as a user event, which means that if your using a NSFetchedResultsController
you need to manually tell the context to process the changes by calling processPendingChanges()
or save the context by calling save()
in order for it to pick up the changes. It also does not wrap the block in an autorelease pool.
Calls to performAndWait(_:)
are reentrant. This means that if you call performAndWait(_:)
from within another perform block they will be executed inline.
Conclusion
When interacting with a context or its managed objects always use a perform method to make sure that the work is executed on the right queue. An exception to this rule is when you want to access a view context and you are already on the main queue. In that case you don’t need to use one of the perform methods.
Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.