Episode #12

Importing into Core Data

22 minutes
Published on April 19, 2012
In this screencast I'll pull down data from an API, map the JSON to a Core Data Managed Object and import them in bulk on a background thread. Then we'll display the imported content in a UITableView using NSFetchedResultsController.

Episode Links

This project requires mogenerator to be installed. You can install it with homebrew:

brew install mogenerator

Having mogenerator invoked automatically after each build

Add a new Run-Script Build Phase with the following contents:

MODELS_DIR="BeerBrowser/DataModel"
DATA_MODEL_PACKAGE="$MODELS_DIR/Beers.xcdatamodeld"
CURRENT_VERSION=`/usr/libexec/PlistBuddy "$DATA_MODEL_PACKAGE/.xccurrentversion" -c 'print _XCCurrentVersionName'`
mogenerator --template-var arc=true --model "$DATA_MODEL_PACKAGE/$CURRENT_VERSION" --output-dir "$MODELS_DIR/"

Core Data-friendly nil support on NSDictionary

#import "NSDictionary+ObjectForKeyOrNil.h"

@implementation NSDictionary (ObjectForKeyOrNil)
- (id)objectForKeyOrNil:(id)key {
    id val = [self objectForKey:key];
    if ([val isEqual:[NSNull null]]) {
        return nil;
    }

    return val;
}
@end

Mapping from JSON to Core Data

This is a manual approach to mapping from JSON objects. It's also possible to devise a more "clever" convention-based scheme.

- (void)updateAttributes:(NSDictionary *)attributes {
    self.name       = [attributes objectForKeyOrNil:@"name"];
    self.address1   = [attributes objectForKeyOrNil:@"address1"];
    self.address2   = [attributes objectForKeyOrNil:@"address2"];
    self.city       = [attributes objectForKeyOrNil:@"city"];
    self.state      = [attributes objectForKeyOrNil:@"state"];
    self.country    = [attributes objectForKeyOrNil:@"country"];
    self.postalCode = [attributes objectForKeyOrNil:@"postalCode"];
    self.details    = [attributes objectForKeyOrNil:@"description"];
    self.website    = [attributes objectForKeyOrNil:@"website"];
}

Fetching an entity by a property

+ (Brewery *)breweryWithServerId:(NSInteger)serverId 
       usingManagedObjectContext:(NSManagedObjectContext *)moc {
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[Brewery entityName]];
    [fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"serverId = %d", serverId]];
    [fetchRequest setFetchLimit:1];

    NSError *error = nil;
    NSArray *results = [moc executeFetchRequest:fetchRequest error:&error];
    if (error) {
        NSLog(@"ERROR: %@ %@", [error localizedDescription], [error userInfo]);
        exit(1);
    }

    if ([results count] == 0) {
        return nil;
    }

    return [results objectAtIndex:0];
}

Fetched results controller initialization

- (void)loadBreweries {
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[Brewery entityName]];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:@"name", @"city", @"state", @"country", nil]];
    [fetchRequest setFetchBatchSize:40];
    NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                    managedObjectContext:[[BeersDataModel sharedDataModel] mainContext]
                                                                      sectionNameKeyPath:nil
                                                                               cacheName:nil];
    [_fetchedResultsController performFetch:nil];
}

Listening for changes to a background Managed Object Context

// in viewDidLoad...
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(contextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];

- (void)contextDidSave:(NSNotification *)notification {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSManagedObjectContext *mainContext = [[BeersDataModel sharedDataModel] mainContext];
        [mainContext mergeChangesFromContextDidSaveNotification:notification];

        [self loadBreweries];
        [self.tableView reloadData];
    });
}

Implementing the UITableViewDataSource methods with NSFetchedResultsController

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[_fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id<NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    Brewery *brewery = [_fetchedResultsController objectAtIndexPath:indexPath];

    /* Standard UITableViewCell stuff */

    return cell;
}
Want more? Subscribers can view all 585 episodes. New episodes are released regularly.

Subscribe to get access →

Source Code

View on GitHub Download Source