In this episode we continue on our exploration of RestKit, this time with a focus on CoreData. It turns out to be fairly easy to change our existing code to support saving the responses into NSManagedObject classes in a database.
Episode Links Episode Source Code RestKit 0.20.0-rc1 - API Docs RKEntityMapping reference RKManagedObjectStore reference Induction - Polyglot Database GUI - This is free, but crashed on me during the episode. Base - SQLite GUI - Base is a great app, and can be bought on the Mac App Store for around $29. Navicat SQLite - Don't let the website scare you away, the app is great. It's a pricey option, though, with licenses ranging from $69-$140+. Making our model objects with with Core Data The first step is to create the CoreData model. Each of the entities match the class names & properties we've already created. In addition, we make sure that we've specified the Class Name explicitly to match our class name we've already created. Our models currently inherit from NSObject, but we'll need to change that to NSManagedObject to work with CoreData. Next we'll have to specify the property list as @dynamic. Lastly, we'll have to change our collection types to match what we've modeled in CoreData. #import <CoreData/CoreData.h> @interface BeerStyle : NSManagedObject @property (nonatomic, strong) NSNumber *styleId; @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *styleDescription; @property (nonatomic, copy) NSString *category; @property (nonatomic, strong) NSSet *beers; @end @implementation BeerStyle @dynamic styleId, styleDescription, name, category, beers; @end #import <CoreData/CoreData.h> @class BeerStyle, Brewery; @interface Beer : NSManagedObject @property (nonatomic, strong) NSString *abv; @property (nonatomic, strong) NSString *ibu; @property (nonatomic, strong) NSString *labelIconImageUrl; @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSOrderedSet *breweries; @property (nonatomic, strong) BeerStyle *style; @property (nonatomic, readonly) NSString *brewery; @end @implementation Beer @dynamic abv, ibu, labelIconImageUrl, name, breweries, style; - (NSString *)brewery { return [[self.breweries firstObject] name]; // had to change this to work with sets } @end #import <CoreData/CoreData.h%gt; @interface Brewery : NSManagedObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *website; @end @implementation Brewery @dynamic name, website; @end Now we have a working model, next we deal with the CoreData setup. For that, we can use RKManagedObjectStore, which can really simplify the entire process. Setting up CoreData using RKManagedObjectStore I decided to encapsulate this inside of a class, so I created BeerInfoDataModel. #import <RestKit/RestKit.h> @interface BeerInfoDataModel : NSObject @property (nonatomic, strong) RKManagedObjectStore *objectStore; + (id)sharedDataModel; - (void)setup; @end We start off with a simple singleton implementation: #import "BeerInfoDataModel.h" #import <CoreData/CoreData.h> @interface BeerInfoDataModel () @end @implementation BeerInfoDataModel + (id)sharedDataModel { static BeerInfoDataModel *__sharedDataModel = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ __sharedDataModel = [[BeerInfoDataModel alloc] init]; }); return __sharedDataModel; } - (void)setup { } @end Next we need to do the work in setup. - (NSManagedObjectModel *)managedObjectModel { return [NSManagedObjectModel mergedModelFromBundles:nil]; } - (id)optionsForSqliteStore { return @{ NSInferMappingModelAutomaticallyOption: @YES, NSMigratePersistentStoresAutomaticallyOption: @YES }; } - (void)setup { self.objectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"beerInfo.sqlite"]; NSLog(@"Setting up store at %@", path); [self.objectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:[self optionsForSqliteStore] error:nil]; [self.objectStore createManagedObjectContexts]; } I've extracted out the model into a method in case we need to change it at some point. By default this will find the model from our bundle and return it. An important step here is the call to createManagedObjectContexts, which is responsible for maintaining your main managed object context (called mainQueueManagedObjectContext). Next, we turn to our MappingProvider to indicate that the mappings should use entities instead of plain model objects. Configuring the Mappings for CoreData We simply need to change how we create the mapping for each method to something like this: RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:@"BeerStyle" inManagedObjectStore:[[BeerInfoDataModel sharedDataModel] objectStore]]; The rest of the mapping stays the same. Changing the view controllers We need to tell the view controllers about our object store & managed object context so that it can save the responses in the database. The changes are shown in bold. - (void)loadBeerStyles { NSIndexSet *statusCodeSet = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); RKMapping *mapping = [MappingProvider beerStyleMapping]; RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"/v2/styles" keyPath:@"data" statusCodes:statusCodeSet]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://api.brewerydb.com/v2/styles?key=%@", BREWERY_DB_API_KEY ]]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; <strong>RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]]; RKManagedObjectStore *store = [[BeerInfoDataModel sharedDataModel] objectStore]; operation.managedObjectCache = store.managedObjectCache; operation.managedObjectContext = store.mainQueueManagedObjectContext;</strong> [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) { self.styles = mappingResult.array; [self.tableView reloadData]; [SVProgressHUD dismiss]; } failure:^(RKObjectRequestOperation *operation, NSError *error) { NSLog(@"ERROR: %@", error); NSLog(@"Response: %@", operation.HTTPRequestOperation.responseString); [SVProgressHUD showErrorWithStatus:@"Request failed"]; }]; [operation start]; } An identical change needs to happen in BeersViewController as well. Trying it out After running the application, we notice that it works the same as before, however this time the data has been saved to a sqlite database. We look at the path that was logged out so we can easily navigate there in the Terminal, and inspect the contents with sqlite3. $ sqlite3 beerInfo.sqlite > SELECT COUNT(*) FROM ZBEER; 50 > .exit And that's it! RestKit is now saving responses to the database.