A couple of weeks ago we decided to fully switch to Core Data for persistent data storage on our iOS apps. This was a bold decision that would bring us more coherent data representations, performance improvements and make future data migrations a breeze…or so we thought. Whilst there certainly were a lot of gains from switching to Core Data it took us a lot longer than anticipated, and we ran into some unexpected issues and limitations. Since we are surely not the last ones moving existing apps to Core Data, we would like to share some of our lessons learned.
Note: This post assumes that you are already familiar with the concepts of Core Data and its classes. If you are new to the subject, we recommend reading the Core Data programming guide by Apple.
Lesson 1: The more you respect MVC, the easier your migration will be
Core Data knows no mercy with developers who disrespect the MVC principles. Because model objects can get deallocated and reloaded at the will of their managing NSManagedObjectContext you can never rely on them being around unless you specifically retain them (which isn’t a good idea). It is thus unwise to set the model objects as the direct receivers of callbacks (for example, image download). Instead, route all calls through the appropriate controller, which manages the NSManagedObjectContext of the data object, and retrieve the data objects at the time when you need them to store the downloaded information. This does not only solve the availability issue but also makes for much cleaner code as you are forced to minutely adhere to the MVC standards.
Lesson 2: Data migrations are a breeze…if you have XCode >= 4.0.2
If you are already partly using Core Data in your application you will probably need to update your data model, and chances are that you need to provide a migration mapper. Luckily there are some great tutorials on the subject out there; however, you can still lose a lot of time if you forget either of the following:
- Turn off automatic data migration (when initializing the persistent data store manager, set NSMigratePersistentStoresAutomaticallyOption to NO)
- If you are still running XCode 4.0.0, upgrade to at least XCode 4.0.2. Previous versions have a bug that prevents the migration mappers from being placed in the proper location at link time. Believe us, we learned the hard way.
Lesson 3: Multithreading and Core Data can work very well if you plan ahead
Perhaps the most annoying part about Core Data is that, in its current implementation, the two objects you interact with the most (NSManagedObjectContext and NSManagedObject) are both not yet threadsafe. However, there are best practices, using workarounds, for both updating and loading objects on secondary threads. If you familiarize yourself with them before implementing your designs you will find it pretty easy to create concurrent applications using Core Data. Apple has a great guide on Core Data and concurrency which is well worth the read and nicely explains what to do in both situations.
Lesson 4: NSFetchedResultsController is not always useful
In order to make it easier to use UITableView together with Core Data, Apple has invented the NSFetchedResultsController, which manages the NSManagedObjects associated with a particular fetch request and monitors an associated NSManageObjectContext for changes to those objects. While this setup is great if there is only one UITableView being displayed and there are few changes being made to the underlying data objects, we’ve found that using NSFetchedResultsController, under certain conditions, can adversely effect the (felt) performance of your app. When you have data sets with many changes or if there are frequent changes being made to underlying data objects, it is advisable to manually “bundle” those changes and entirely reload the UITableView instead of incrementally reloading the cells affected. If you meet one of those two cases, consider implementing your own update routine and temporarily deactivating the update mechanism of the NSFetchedResultsController by setting its delegate to nil.
Lesson 5: Performance, performance, performance!
To get the best performance out of Core Data keep the following in mind as you sketch up your refactoring:
- Multiple small queries are slower than one larger query since every call to performRequest: hits the disk
- Complex filter predicates can significantly hamper the performance of your queries, keep them as simple as possible
- Fetching relationships is expensive; avoid them if they do not provide a significant advantage. Flatten your data model where you need to load data together.
- Only store small data objects directly in CoreData, store larger objects in files and only save the path to them in CoreData (eg. images)
We hope these insights will help you when planning your own migration to CoreData. Let us know if you find other valuable tricks for dealing with CoreData!
Thanks for reading!
~The Pulse iOS team