Episode #51

Intro to RestKit: Mapping

25 minutes
Published on January 31, 2013

This video is only available to subscribers. Get access to this video and 584 others.

RestKit is a framework that aims to simplify the "plumbing" of your application to allow you to focus on your core features. In this screencast, I focus on fetching JSON from an API and mapping it onto our own objects using RestKit's mapping features.

Episode Links

The API

For the API in this episode, I used BreweryDB, who was gracious enough to let you use a temporary API key for the sample app. It should last at least for the next few weeks to make it easier for you to follow along. If the API key doesn't work for you you'll get an error, in which case you can sign up for your own key (which is free).

The API documentation can be found here: http://www.brewerydb.com/developers/docs.

The models

We are only interested in a subset of the information provided by the API. For this information, we create 3 classes to represent the structure we're after: BeerStyle, Beer, and Brewery.

@interface BeerStyle : NSObject
@property (nonatomic, assign) NSInteger styleId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *styleDescription;
@property (nonatomic, copy) NSString *category;
@end

@interface Beer : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *ibu;
@property (nonatomic, copy) NSString *abv;
@property (nonatomic, copy) NSString *labelIconImageUrl;
@property (nonatomic, strong) NSArray *breweries;
@property (nonatomic, readonly) NSString *brewery;
@end

@interface Brewery : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *website;
@end

The mappings

Next we simply tell RestKit how to map the JSON structure onto these objects.

@implementation MappingProvider

+ (RKMapping *)beerStyleMapping {
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[BeerStyle class]];
    [mapping addAttributeMappingsFromArray:@[@"name"]];
    [mapping addAttributeMappingsFromDictionary:@{
        @"id": @"styleId",
        @"description": @"styleDescription",
        @"category.name": @"category"
     }];
    return mapping;
}

+ (RKMapping *)beerMapping {
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Beer class]];

    [mapping addAttributeMappingsFromArray:@[@"name", @"ibu", @"abv"]];
    [mapping addAttributeMappingsFromDictionary:@{@"labels.icon": @"labelIconImageUrl"}];
    [mapping addRelationshipMappingWithSourceKeyPath:@"breweries"
                                             mapping:[MappingProvider breweryMapping]];
    return mapping;
}

+ (RKMapping *)breweryMapping {
    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Brewery class]];
    [mapping addAttributeMappingsFromArray:@[@"name", @"website"]];
    return mapping;
}

Note the difference between adding attributes from the array (for the cases where our property matches the attribute in the JSON document. For the ones that differ, we can map that with a dictionary, where the left side key is the JSON attribute and the value is our model's property. We can also flatten out structures, as you see for category.name mapping to category on BeerStyle.

Finally, to represent nested child objects, we add a relationship mapping. RestKit automatically converts a JSON array to an object array using the mapping provided, so here we'll end up with an array of Brewery objects.

Fetching data using the mappings

    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];
    RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request
                                                                        responseDescriptors:@[responseDescriptor]];
    [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        self.styles = mappingResult.array;
        [self.tableView reloadData];
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        NSLog(@"ERROR: %@", error);
        NSLog(@"Response: %@", operation.HTTPRequestOperation.responseString);
    }];

    [operation start];