Blog

Tales from Core Data

I’ve been working with Core Data for the last couple of weeks and it hasn’t been an easy journey. There is a lot of information about it, but most cases I have found confusing answers to my questions, so I decided to share what I have learned so far. This isn’t a definitive guide, but I hope this will help other fellow developers in their journey to start using Core Data.

Documentation

Core Data has lots of documentation. Yes. Lots. The problem is how this information is arranged. Apple’s Documentation has a very detailed guide about how to use the different APIs, but lacks a practical approach. My recomendation is to start with the 4th issue of ObjC.io, which covers the fundamentals and advanced topics in a quite straighforward manner.

The key concepts you really have to understand are: NSManagedObjectContext, NSManagedObject, NSPersistentStoreCoordinator and NSPersistentStore. If you don’t know which are the roles of each one of these classes, then you’ll be lost.

Core Data setup and Magical Record

Core data requires some boilerplate code that can be easily lifted up by a wrapper. I tried using two libraries for this:

Objective-Record: It’s a quite simple and elegant library, but lacks a concurrency model out the box.

Magical-Record: Inspired by ActiveRecord, it is a quite complete library that provides different approaches for saving changes on the MOC objects, search, etc. I finally ended up using this one.

I’ll not go through the whole setup section, because there is enoughinformationaboutit. One of the things that is important to point out is that this wrapper comes with a parent and child managed object contexts:

  • Child Managed Object Context (defaultContext on the wrapper): Runs on the main thread
  • Parent Managed Object Context (rootContext on the wrapper): It is used to save all changes on the object graph to the NSPersistantStore.

If you don’t know what this types means, don’t worry, you’ll figure it out in the following sections.

Core Data and Concurrency

All operations in your object graph will start through a NSManagedObjectContext (MOC). This is the most sensitive part of your concurrency scenario, because CoreData applies a lock to MOC object. In other words, if you try to access a MOC object from another thread, while saving the changes on the main thread in the same MOC object, it will be highly probable that it will derivate in a deadlock in your app.

The framework defines three concurrency types or scenarios for MOC objects, from Apple’s Documentation:

  • Confinement (NSConfinementConcurrencyType) For backwards compatibility, this is the default. You promise that context will not be used by any thread other than the one on which you created. In general, to make the behavior explicit you’re encouraged to use one of the other types instead. You can only use this concurrency type if the managed object context’s parent store is a persistent store coordinator.
  • Private queue (NSPrivateQueueConcurrencyType) The context creates and manages a private queue.
  • Main queue (NSMainQueueConcurrencyType) The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.

This leads to the question, “Should create a MOC object per thread?” The answer would be it depends. If you’re going to have 30 threads processing and saving changes into the NSPersistantStore at the same time, it is probable you might corrupt your storage file (XML, SQLite). If you’re going to build a scenario where you define roles per threads (Read, Update/Save/Delete), it’s higly probable you’re going to have a smoother process, and it’s Apple’s recommendation in their documentation: Use Thread Confinement to Support Concurrency.

Before presenting an example, let’s review how Magical Record performs saving between nested contexts and to the persistant store.

Magical Saves

Magical record provides two options to perform saving:

[defaultContext MR_saveOnlySelfAndWait]; //Save to the parent context only. [defaultContext MR_saveToPersistentStoreAndWait] // Continue saving parent contexts until the changes are present in the persistent store.

As it says in the comment “MR_saveOnlySelfAndWait” will push changes to its parent, but this doesn’t save to the persistantStore, unless you’re using MagicalRecord’s rootContext. “MR_saveToPersistentStoreAndWait” will continue saving until it gets to the rootContext and pushes all changes to the persistant store. Take a look to the NSManagedObjectContext+MagicalSaves category on MagicalRecord to have a complete picture of this process.

Which one to use? If you’re fetching batches of 100 items, and saving way down until to the persistStore per each one new object this will be higly expensive. Remember CoreData will convert all changes in your object graph (insert,delete,update) to the representation of your persistantStore (XML or SQLite).

Now, we can go next to our implementation example.

An example of implemention

Our example will use the nested contexts and save to the persistant store every 10 changes on the root MOC. The high level implementation would look like:

Example of Implementation

The MyCoreDataManager class should be singleton and subscribe to the ‘NSManagedObjectContextObjectsDidChangeNotification’ notification coming from the parent MOC object like this:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didManagedObjectContextChanged:) name:NSManagedObjectContextObjectsDidChangeNotification object:[NSManagedObjectContext MR_rootSavingContext]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveToDataBase) name:UIApplicationDidEnterBackgroundNotification object:nil];

The implementation of the 'didManagedObjectContextChanged’ selector would look like this:

- (void)didManagedObjectContextChanged:(NSNotification *)notification { self.changesCounter += 1; if (self.changesCounter > 10) { [self saveToDataBase]; } }

     - (void)saveToDataBase { self.changesCounter = 0; if([NSManagedObjectContext MR_rootSavingContext].hasChanges) [[NSManagedObjectContext MR_rootSavingContext] MR_saveToPersistentStoreWithCompletion:nil]; }

Using a NSManagedObjectContext per thread

Magical Record provides a very handy method to create a new NSManagedObjectContext per thread:

NSManagedObjectContext *threadContext = [NSManagedObjectContext MR_context];

This methods creates a new NSManagedObjectContext and sets MagicalRecord’s defaultContext as its parent. However after having an implementation, a very strange bug appeared so I had to roll back the implementation similar to the previous example. Anyways this approach will be deprecated on Magical Record 3.0.

Conclusion

After lots of blood and pain I started to really enjoy CoreData. I find it’s an incredible framework to handle complex objects graphs while proving persistant options.

Rod Garciacoredata, ios, devstories