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:
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) {
}