While working with Core Data, iOS Developers need to take into account performance impacts of the design. We might test with a couple of photos and expect our users to load a couple hundred photos into our application and test with that scale.
However, as soon as you ship the application, some user will load 100,000 photos into it and then file a claim that your app performs poorly.
In the following article we will discuss some of the key considerations when working with core data that can provide you a guide to improve your apps performance and reduce the memory spikes.
Choosing the right Store
In-memory Store: This is not a persistent store because it is never written out to disk.
Binary Store: This is effectively a serialized version of the object graph written out to disk. For small amount of data they can give the best performance as they are read directly from the memory and the disk is hit only when the file is read into memory and when it is saved back out. This causes binary stores to have a larger memory footprint than a SQLite.
SQLite Store: Stores the object graph in a relational database.
In the most common cases, SQLite is the store option to use for application development.
You no longer need to load the entire data set into memory to work with it. Because the data is being stored in a relational database, your application can scale to a very large size.
Where to put Binary Data
One of the easiest ways to kill performance in a Core Data application is to stick large amounts of binary data (blobs) into frequently accessed tables.
For relatively small data sizes it’s reasonable to keep BLOBs in the data store, while larger ones should probably be kept separate.
Managing BLOBs in the Entity That Uses Them
The simplest method is just to put the binary data right in the associated entity.
For example in case of recipes, just add a new imageData property to the Recipe entity with its type set to Binary.
This is convenient and you will not load all the images in memory when recipes are loaded, however you need to keep track of memory and faulting. As soon as fault fires the images ill the be there in memory and resident till object is deallocated.
Putting Blobs in separate files
If you are developing an application for iOS 6.0 or you can turn on external binary storage in the model and let Core Data handle how to store the images.
With this setting enabled Core Data decides whether the image is small enough (500kb or less) to store inside of the SQLite file or whether it is too big(1mb or larger) and therefore store on disk separately
Other Memory Management tips
Use Undo Manager only if needed
It is useful when you need it, but it also increases memory use, since objects will be kept in memory just in case you need to undo an operation. On iOS, undo manager is not created by default, so none will exist unless you create one.
If you don’t need certain object or all the objects in your managedObject Context (MOC) call the reset method on your MOC and it will make it act as it was just initialized and reduces your memory spikes by a noticeable amount if you had lot of data as a result of earlier fetch requests.
Some Fetch Optimization Tips
It is always easy to ask for more data than what the user will actually need, some of the techniques mentioned below will help in optimizing fetch requests
Set fetch limits
If there can be hundreds of matching objects and you can only show 15 or 20, don’t fetch all.
You can use the fetchlimit property on fetch request to limit the number of records to fetch and for further records you can use the fetchoffset property
Optimize your predicates
Text searches are expensive and number comparison are faster and cheaper. If you have a complex predicate with one or more comparison conditions, put the number comparison first and if first test fails the rest will be short-circuted.
If an entity has relations the relations are fetched as faults which is good for memory. But if you need more performance you may want to pre-fetch the relationship ahead of time.
You do this with NSFetchRequest’s setRelationshipKeyPathsForPrefetching: method.
Normally managed objects from data store are returned as fault objects, and the property values are filled in on demand. If you know in advance you will be needing the properties too you can for performance reasons just request a non-fault object with all the property data. This is a simple flag on the fetch request:
Prevent property loading
Conversely if you know you will not needing the properties and are for e.g. only validating an object existence using their ID , you can completely restrict the properties from loading in memory or even in the row cache and reduce your apps memory foot print significantly.
Core Data is very useful for managing your data but with large amounts of data it can soon become clunky if you are not careful about how the data is used in your application.
We have briefly discussed some and not all of the things you can do to keep your app performance high with and your memory usage low. Our hope is that this will help you optimize your core data design and operations.