Building RESTful iOS apps using RestKit

RestKit is an Objective-C framework for iOS that aims to make interacting with RESTful web services simple, fast and fun. It combines a clean, simple HTTP request/response API with a powerful object mapping system that reduces the amount of code you need to write to get stuff done.

This post is not about getting started with RestKit. There are several excellent posts already available that talk about it. This is about how you can structure your apps to better leverage the RestKit functionality and keep the code maintainable and easy to follow.

RKRouter is something that isn’t described in great detail in the existing guides. It is also something that is very powerful and is great to put together all the routes for your REST requests. An RKRouter instance is responsible for generating NSURL objects with a given base URL and a route set. It is used to centralize the knowledge about the URL’s that are used by the application.

URL’s can be generated by the router in three ways:

  1. By name. Named routes link a symbolic name with a path and an HTTP request method. 
  2. By object. Routes can be defined by class and HTTP request method. When a URL is requested from the router for an object, the router will identify the most appropriate route for the object and instantiate an NSURL with the route’s path pattern and interpolate it against the object.
  3. By object relationship. Routes can be defined for relationships to other objects. When a URL is requested from the router for a relationship, the router will retrieve the appropriate route for the relationship from the route set and interpolate the route’s path pattern against the source object.

Let see how we can organise our app to leverage the capabilities of RKRouter. We create an interface for each of our model that we expect in the response from our RESTful API.

@interface GHUser

@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSString* login;
@property (nonatomic, retain) NSString* email;
@property (nonatomic, retain) NSArray* repositories

+ (RKObjectMapping *) responseMapping;

@end

@implementation GHUser

+ (NSDictionary*)elementToPropertyMappings {
  return @{
    @"name": @"name",
    @"login": @"login",
    @"email": @"email",
  };
}

+ (RKObjectMapping *) responseMapping {
  // Create an object mapping.
  RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[GHUser class]];
  [mapping addAttributeMappingsFromDictionary:[GHUser elementToPropertyMappings]];

  // Add some relation mappings (if any.)
  [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"repositories" toKeyPath:@"repositories" withMapping:[GMRepository responseMapping]]];

  return mapping;
}

@end

We want to separate the object mapping for each of the objects into their own class rather than putting them at one place where we declare the routes in order to make this manageable. We do this by creating a static method called responseMapping on each of our model which we can then use to get the mapping that we need. If you also need to send some objects as request body, you can declare a method like requestMapping to create a mapping that can be used in a RKRequestDescriptor. The simplest implementation of the requestMapping could be return [[GHUser responseMapping] inverseMapping];.

With this done, we can start creating some routes. Let’s have a interface called RestClient that will manage all the routes and also a shared object manager that holds everything.

// -- RestClient.h -- //

// Declare constants.
extern NSString *const kRestClientListUsersRouteName;

// Interface declaration.
@interface RestClient : NSObject

    + (instancetype)sharedClient;

@end

// -- RestClient.m -- //

// Define constants.
NSString *const kRestClientListUsersRouteName = @"LIST_USERS_ROUTE";

// Interface implementation.
@implementation RestClient

static RestClient *sharedClient = nil;

- (id)init {
    self = [super init];
    if (self) {
        [self initializeObjectManagers];

        // Set shared manager if nil
        if (nil == sharedClient) {
            [RestClient setSharedClient:self];
        }
    }

    return self;
}

- (void)initializeObjectManagers {
    // Uncomment below to enable rest kit logging.
    // RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);

    // Configure the object manager.
    RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"https://github.com/"]];
    objectManager.requestSerializationMIMEType = RKMIMETypeJSON;
    [RKObjectManager setSharedManager:objectManager];

    // -- Declare routes -- //

    // List users route. We create a named route here.
    RKObjectMapping *userMapping = [GHUser responseMapping];
    RKResponseDescriptor *listUsersResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodGET pathPattern:@"api/v2/users" keyPath:@"users" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:listUsersResponseDescriptor];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithName:kRestClientListUsersRouteName pathPattern:@"api/v2/users" method:RKRequestMethodGET]];

    // Get user by name route. We create a class route here.
    RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodGET pathPattern:@"api/v2/users/:name" keyPath:@"user" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:userResponseDescriptor];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[GMuser class] pathPattern:@"api/v2/users/:name" method:RKRequestMethodGET]];

    // Create user route. We want to send the user in the body of POST request.
    RKObjectMapping *userRequestMapping = [GMUser requestMapping];
    RKRequestDescriptor *userRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:userRequestMapping objectClass:[GMUser class] rootKeyPath:@"user" method:RKRequestMethodPOST];
    [objectManager addRequestDescriptor:userRequestDescriptor];
    RKResponseDescriptor *createUserResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodPOST pathPattern:@"api/v2/users" keyPath:@"user" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    [objectManager addResponseDescriptor:createUserResponseDescriptor];
    [objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[GMUser class] pathPattern:@"api/v2/users" method:RKRequestMethodPOST]];
}

#pragma mark - Shared Client
+ (void)setSharedClient:(RestClient *)client {
    sharedClient = client;
}

+ (instancetype)sharedClient {
    return sharedClient;
}
@end

As you see, we add up all the routes for our REST requests in our rest client. If we want to separate things further, we might create a new static method in the GHUser interface that builds up the routes specific to the user. Anyway, after this, all we need to do is to initialise the rest client to store all the routes in. You can do this in you AppDelegate like so: [[RestClient alloc] init];.

Now, executing the requests is a simple call to the appropriate RKObjectManager method. e.g. To list all the users,

// Get list of users.
[[RKObjectManager sharedManager] getObjectsAtPathForRouteNamed:kRestClientListUsersRouteName
                                                            object:nil
                                                        parameters:parameters
                                                           success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                               // Do something with mappingResult.array.
                                                           }
                                                           failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                               // Do something.
                                                           }];

// Get a user by user name.
[[RKObjectManager sharedManager] getObject:[GHUser userWithName:@"pulkit110"] path:nil parameters:nil
                                   success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                       // Do something with [mappingResult firstObject]
                                   }
                                   failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                       // Do something
                                   }];

// Create a new user.
[[RKObjectManager sharedManager] postObject:user path:nil parameters:nil
                                    success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {

                                    }
                                    failure:^(RKObjectRequestOperation *operation, NSError *error) {

                                    }
Published 24 Dec 2013

I build mobile and web applications. Full Stack, Rails, React, Typescript, Kotlin, Swift
Pulkit Goyal on Twitter