The various Apple frameworks are hit and miss. Some of them, such as UIKit, are spot on in their design and its consistency makes UI development simple and easy. Core Data, the persistence framework of iOS, is sadly not so gifted. The SDK is clumsy, easy to get wrong, and worst of all requires a lot of boilerplate code to manage your data.
Until iBoost, anyway.
The CoreDataStore is the featured player in iBoost when it comes to wrapping the CoreData framework. I conceived of this layer after completing my first Core Data-based project. During a post-mortem review, I was disgusted with all of the low-level, hard-to-understand code that was the result. When I write my persistence code, I expect it to be focused on logic and my app’s data structures, not the silliness I was forced to add. Maybe you feel the same way? NSPersistentStoreCoordinators, NSManagedModels, and NSManagedObjectContexts — OH MY!
Let’s see how the CoreDataStore fixes everything.
Initializing and Clearing Core Data
There is only one requirement to use Core Data, and the CoreDataStore, in your project: include the CoreData framework in your project! That’s it. In fact, I recommend not even clicking the “Use Core Data” checkbox when setting up your project. If you do, you’ll want to delete all the Core Data boilerplate that you’ll find in your App Delegate. You won’t need it.
(And by the way, Apple, you shouldn’t be putting that code there…that was a poor choice on your part. Shame. Shame!)
Ready to see how you initialize the CoreDataStore?
Wait, where’s the code? There is none! Once you start using CoreDataStore, it will automatically create your persistence store it if it hasn’t been already. I told you it was going to be easy.
Now, here’s a bonus. Many frameworks don’t make this available, but if you ever wanted to erase all your data so that you can start fresh again, here’s what you do:
[[CoreDataStore mainStore] clearAllData];
This is really handy during unit testing when you want to erase any persisted data between tests.
CoreDataStore supports two styles of API:
- ActiveRecord Style – This is the preferred API which lets you operate directly on your Core Data objects in the style of the ActiveRecord pattern. It does require you generate your CoreData objects with their entity names. Since this is already common and a best practice, you’ll most likely be using this style.
- Factory Style – This style doesn’t require generating Core Data objects and requires the client code to specify entity names as an NSString. It works exactly the same, but is less intuitive and prone to errors since it’s not type-checked.
I’ll show both styles. First, we’ll review ActiveRecord Style. You should generate all your Core Data entities as classes being sure the class names match the entity names. This is usually accomplished via XCode, but I highly recommend the mogenerator utility since it uses a better design pattern for generating the classes as two per entity.
Here’s how you create a new object:
Customer *apple = [Customer create]; apple.name = @"Apple"; [[CoreDataStore mainStore] save];
Here you can see that we create the Customer object directly by calling -create right on it. We save to something called the mainStore. This is an important concept. The mainStore is considered the default NSManagedObjectContext for all operations. For most apps, it’s all you’ll need. It’s essentially using a single database handle for your entire app. One situation where you might want to create your own context is when you want to handle a set of operations that you can rollback at a given time. If you want to perform the same creation with a custom context, the code would look like:
CoreDataStore *customStore = [CoreDataStore createStore]; Customer *apple = [Customer createInStore:customStore]; apple.name = @"Apple"; [customStore save];
All of the CoreDataStore methods support this syntax to let you choose a custom CoreDataStore to perform your operations, but the default always is the mainStore. You’ll likely only need this context.
Now, if you’re not generating your objects, you create data another way:
NSManagedObject *apple = [[CoreDataStore mainStore] createNewEntityByName:@"Customer"]; apple.name = @"Apple"; // always remember to save! [[CoreDataStore mainStore] save];
As you can see, you need to pass the entity name as a string to create the object. Otherwise, the syntax is similar. Note also that you still have the ability to create your own context rather than use the mainStore.
Deleting data is the same in either style:
Remember, you’ll need to save, too. I won’t show this code anymore, but review the previous section to see how it’s done.
There are many ways to query using CoreDataStore. Here are a series of examples that should be easy to understand. (If they weren’t, iBoost wouldn’t be doing its job now would it?)
// all methods NSArray *allCustomers = [Customer all]; NSArray *allCustomers = [Customer allOrderedBy:@"name" ascending:YES]; NSArray *activeCustomers = [Customer allForPredicate:[NSPredicate predicateWithFormat:@"active = 1"]]; NSArray *activeCustomers = [Customer allForPredicate:[NSPredicate predicateWithFormat:@"active = 1"] orderBy:@"name" ascending:YES]; // all methods -- custom data store NSArray *allCustomers = [Customer allInStore:myDataStore]; NSArray *allCustomers = [Customer allOrderedBy:@"name" ascending:YES inStore:myDataStore]; NSArray *activeCustomers = [Customer allForPredicate:[NSPredicate predicateWithFormat:@"active = 1"] inStore:myDataStore]; NSArray *activeCustomers = [Customer allForPredicate:[NSPredicate predicateWithFormat:@"active = 1"] orderBy:@"name" ascending:YES inStore:myDataStore]; // first methods UserSettings *settings = [UserSettings first]; Customer *apple = [Customer firstWithKey:@"name" value:@"Apple"]; // first methods -- custom data store UserSettings *settings = [UserSettings firstInStore:myDataStore]; Customer *apple = [Customer firstWithKey:@"name" value:@"Apple" inStore:myDataStore];
Use whatever you need. There should be no question about what the methods do. The -all methods return all data that matches the query, if there is any. The -first methods simply return the first match. In most situations, you should expect only one result. So, you should be attempting to match singleton data (such as user settings) or a specific match with a unique name.
The goal of CoreDataStore is to give you access to Core Data without all the ugly boilerplate you’d normally need. I think it does that well, what do you think? In Part 3, I’ll review the Core Data Store, a handy set of methods that make using Core Data much easier!
In Part 3, I’ll introduce the Message Center, a replacement for the NSNotificationCenter that adds the ability to insert code between posting and receiving messages.