Paradigm X

Vision quests of a soulhacker

Introducing RestKit

RestKit is an amazing Objective-C framework for iOS that aims to make interacting with RESTful web services simple, fast and fun. As said on its homepage:

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.

RestKit provides several components:

  1. An HTTP client component based on NSURLConnection. It’s not as feature-packed as ASIHTTPRequest1, but it does its part very well in most cases. It supports GET POST PUT DELETE, SSL & HTTP AUTH, multi-part, etc. It even provides a request queue to manage the HTTP requests(mostly implicitly), making them more memory and bandwidth efficient.
  2. A powerful but elegant object mapping system, which uses idiomatic key-value coding mechanism to define and implement the round-trip transformation between JSON2 string and native Objective-C class. Well, the magic behind it is called SOCKit3.
  3. A persistent layer built on top of the object mapping system, fully integrated with CoreData. Can be used as app’s primary data storage or just local cache.
  4. Miscellaneous helpers for better life. Reachability, database seeding, API environments switching, etc.

Other than making another almighty monster its creators focus on the remote-local object (can be transient or persistent through CoreData) mapping. They made it through the ‘common things simple, others possible’ philosophy, which I always love.

RestKit has some documentations but not very well organized. Maybe it’s the biggest problem for beginners. So I’d like to list below some most useful solution recipes I’ve used in real world project, updated progressively.

WARNING: it will be incredibly long anyway.

Setting up

RestKit provides two ways to set up the RestKit client: RKClient and RKObjectManager. If you want to manually handle the return data you should use RKClient. Otherwise, if you want to use the RestKit object mapping system, then RKObjectManager is the one.

// API base (and the whole API environment) can be switched by
#define gkBaseURL @"https://api.domain.com/v2"

RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:gkBaseURL];
NSLog(@"I am your RKObjectManager singleton : %@", manager);

// Or you can use
RKClient *client = [RKClient clientWithBaseURL:gkBaseURL];
NSLog(@"I am your RKClient singleton : %@", client);

RestKit uses a convenient convention: the first initialized client object becomes a singleton and can be referenced by [RKClient sharedClient] or [RKObjectManager sharedManager] correspondingly.

In real world we usually have more than one base URLs, say, one for product environment, the second for testing and third for developing. The base URL can be easily changed globally:

[[RKClient sharedClient] setBaseURL:@"https://api.domain.com/v2d"];

Note that change base URL while app is running may bring some side effects. See the API document for detail.

RKClient acts just like a plain asynchronous HTTP client. You can send any HTTP request through its intuitive interface:

@interface User : NSObject <RKRequestDelegate> {
}

@implementation User

- (void)send {
    // HTTP GET
    [ [RKClient sharedClient] get:@"users/self" delegate:self];

    // HTTP POST. Note the transparent NSDictionary conversion
    NSDictionary* params = 
        [NSDictionary dictionaryWithObjectsAndKeys:_userName, @"userName", 
                                                   _password, @"password", nil];
    [ [RKClient sharedClient] post:@"users/login" params:params delegate:self];

    // HTTP DELETE
    [ [RKClient sharedClient] delete:@"users/13" delegate:self];
}

// The delegate handles the response data when its fully loaded
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {  
    if ([request isGET]) {
        if ([response isOK]) {
            NSLog(@"Data returned: %@", [response bodyAsString]);
        }
    } else if ([request isPOST]) {
        if ([response isJSON]) {
            NSLog(@"POST returned a JSON response");
        }
    } else if ([request isDELETE]) {
        if ([response isNotFound]) {
            NSLog(@"Resource '%@' not exists", [request resourcePath]);
        }
    }
}

By using RKObjectManager you’ll map the web resources to local Objective-C objects, so you need not (and cannot) handle the return data explicitly. Instead you should handle data through the mapping system, which will be discussed below.

Modeling and Mapping

Now the object mapping system. Let’s start with the best practice in RESTful API backed mobile app development:

  1. Analyze the business and make abstraction.
  2. Design the RESTful API spec based on the first step, including all call endpoints, parameters and returning data in JSON format.
  3. Mapping the API spec to native code in designated mobile platform.

Assuming we have a simple API named users/login, accepting POST request with 2 parameters: username and password, doing login check, and returning a encrypted access token and corresponding user object. And as a global convention we encapsulate the payload as response section in a JSON string which also contains a meta section for system level info such as status code and error message. So here it looks like:

{
    "meta": {
        "code": 200
    },
    "response": {
        "token": "931240153764834717 156605143238420655",
        "user": {
            "user_name": "soulhacker",
            "..."
        }
    }
}

It’s simple but not trivial, and very representative. What will the mapping work? Have a look at the code first:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#pragma Mark - Class Meta

@interface Meta : NSObject
@property (readonly) NSString *code;
@end

@implementation Meta
@synthesize code = _code;
@end

#pragma Mark - Class User

@interface User : NSObject
@property (readonly) NSString *userName;
// Many stuff omitted here
@end

@implementation User
@synthesize userName = _userName;
// Many stuff omitted here
@end

#pragma Mark - Class UsersLogin

@interface UsersLogin : NSObject
@property (readonly) Meta *meta;
@property (readonly) NSString *token;
@property (readonly) User *user;
@end

@implementation UsersLogin
@synthesize meta = _meta;
@synthesize token = _token;
@synthesize user = _user;
@end

#pragma Mark - Class UsersLoginRequest

@interface UsersLoginRequest : NSObject <RKObjectLoaderDelegate> 
@property (nonatomic, retain) NSString *userName;
@property (nonatomic, retain) NSString *password;

- (UsersLoginRequest *)initWithUserName:(NSString *)userName password:(NSString *)password;
- (void)login;
@end

@implementation UsersLoginRequest
// Even more stuff omitted here

- (void)login {
    RKObjectMapping *metaMapping = [RKObjectMapping mappingForClass:[Meta class]];
    [metaMapping mapAttributes:@"code", nil];

    RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[User class]];
    [userMapping mapKeyPath:@"user_name" toAttribute:@"userName"];
    // More mapping for userMapping

    RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[UsersLogin class]];
    [mapping mapKeyPath:@"response.token" toAttribute:@"token"];
    [mapping mapKeyPath:@"meta" toRelationship:@"meta" withMapping:metaMapping];
    [mapping mapKeyPath:@"user" toRelationship:@"user" withMapping:userMapping];
    
    // Make the call
    NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:_userName, @"userName", _password, @"password", nil];
    NSString *endpoint = @"users/login?";
    NSString *resourcePath = [endpoint stringByAppendingString:[params queryString]];
    [[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath objectMapping:mapping delegate:self];
}

#pragma Mark - RKObjectLoaderDelegate

- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
    UsersLogin *result = [objects objectAtIndex:0];
    
    NSLog(@"Response code=%@, token=[%@], userName=[%@]", [[result meta] code], [result token], [[result user] userName]);
}

- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
    NSLog(@"Error!");
}

Line 1-35. Firstly we defined all needed model classes: fundamental ones first, ones that contain them next. Just follow the nature of the data structure within the API’s response data.
Line 51-56. Use instances of RKObjectMapping class to define object mappings. Simple mappings are self explained. Note that RKObjectMapping provides several different mapXXX methods to suit different use cases. The key path in mapping definition fully follows Cocoa’s key path style (also support collection operators). Check its document and Apple’s Key Value Coding Guide for detail.
Line 58-61. Complex object mappings are defined by mapKeyPath:toRelationship:withMapping: or similar methods. They map some key path to pre-defined object mappings. As seen in the code, the mapping to class UsersLogin contains simple mapping (line 59) and relationship mappings (line 60-61).
Line 64-67. RKObjectManager provides loadObjectsAtResourcePath:objectMapping:delegate: method to do these steps in order: combine resource path with the global BaseURL (configured within the RKObjectManager singleton), load resource from web services, parse return data, transform to local objects according to pre-defined RKObjectMapping instance, at last call the delegate to handle result.
Line 72-80. Delegate methods didLoadObjects and didFailWithError (required) are called after the object mapping process. Normally fetch the result object and do whatever you want.

Routing

The previous code list is neat except one part. Note line 64-66, in which we hard-code the API endpoint and parameters. In our login scenario it may not be a big problem, but for highly reused objects, repeatedly constructing GET POST PUT DELETE URLs by hand could be real pain in the ass. How about more object-oriented and more elegant way, just like the routing system in Ruby on Rails?

RestKit is most suitable for developers who has RoR background because of its Routing system which is highly alike to RoR’s, but built on Cocoa’s idiomatic key-value coding way. Here is the code sample:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// The router belongs to the object manager, just as mappers
RKObjectRouter *router = [[RKObjectManager sharedManager] router];
  
[router routeClass:[User class] toResourcePath:@"/users/:id"];
[router routeClass:[User class] toResourcePath:@"/users" forMethod:RKRequestMethodPOST];

// Then we can call the GET POST PUT DELETE verbs directly from object manager
User *me = [[User alloc] init];
me.userName = @"soulhacker";
[[RKObjectManager sharedManager] postObject:me delegate:self];

User *sb2nuke = [[User alloc] init];
sb2nuke.id = [NSNumber numberWithInt:13];
[[RKObjectManager sharedManager] deleteObject:sb2nuke delegate:self];

Line 4 set the default route of User class, which means when you call object manager’s getObject postObject putObject deleteObject methods the requests will go to @"/users/:id". This is a colon coded endpoint and what is after the colon is the name of a method in the route class, whose return value will replace the colon part.
Line 5 set a special route for postObject method, which will override the setting in default route.
Line 10 requests @"/users" with HTTP POST method and RestKit will transform me object into post data form as the way defined in object mapper of class User.
Line 14 first call [sb2nuke id] method to get the value and replace :id with it, then requests @"/users/13" with HTTP DELETE method, which (should) remove the user object with id 13 from the server.

So you see, RestKit provides very flexible tools for RESTful web services integration: you can use the network layer and do all data mapping by hand, or you can use the object mapping to do it completely within local object system, or you can tuning it in some intermediate way. No matter which solution you choose remember to align the protocol between the server and client side, and don’t forget to verify them to the Android platform if necessary (there is no RestKit over there for now -_-).

Thus we’ve completed the first part of this introduction guide. Below we will discuss some facilities bundled in RestKit to make our life better.

The Request Queue

Request queue is the most important support player under the hood. RKRequestQueue is behind nearly all network access within RestKit and provides elegant solution for critical memory and bandwidth problems. RKRequestQueue wraps memory management within RestKit framework so you’ll never see any retain release autorelease for RKRequest RKResponse instances. It let us developers focus on the business with very little concern about the memory management.

RKRequestQueue also provides seamless integration with Reachability API (in iOS System Configuration framework), pooling all requests when network is unavailable, and limiting concurrent requests when network becomes reachable to prevent overburden as well.

All the amazing features described above are working without knowing, and maybe the only visible part of RKRequestQueue is its request lifecycle management feature, by which you can cancel ongoing requests to prevent wasting the bandwidth. This is a common situation in mobile apps that when some user action generates a bunch of network requests and the following action actually makes those requests useless (e.g. the view for previous action is dismissed by the later action). By using RKRequestQueue we can do it very easily by its cancelRequest: cancelRequestsWithDelegate: and cancelAllRequests:` methods. And remember that whether and when to call them is fully determined by developers so you can choose the strategy wisely according to the different scenarios. Here is the simplest sample:

- (void)viewWillAppear:(BOOL)animated {
    [RKClient sharedClient] get:@"users/self" delegate:self];
}

- (void)viewWillDisappear:(BOOL)animated {
    [[RKRequestQueue sharedQueue] cancelRequestsWithDelegate:self];
}

The cancelRequestsWithDelegate: method cancel all requests that current controller is delegate for. If there are no one processing it’ll do nothing.

The last but not least. What [RKRequestQueue sharedQueue] returns is the default queue which created automatically when the RestKit client initialized. In most real world apps it’s not enough. For example we may need a queue to handle special resource intensive tasks in a background thread, such as data uploading/downloading, while the default queue keep working for responding user actions. It’s easy:

RKRequestQueue* queue = [[RKRequestQueue alloc] init];
queue.delegate = self;
queue.concurrentRequestsLimit = 2;
queue.showsNetworkActivityIndicatorWhenBusy = YES;

// Add some requests to it and make it start
[queue addRequest:[
    [RKClient sharedClient] requestWithResourcePath:@"users/1" delegate:self]];
[queue addRequest:[
    [RKClient sharedClient] requestWithResourcePath:@"users/2" delegate:self]];
[queue addRequest:[
    [RKClient sharedClient] requestWithResourcePath:@"users/3" delegate:self]];
 
[queue start];

Reachability

Reachability is the problem all mobile apps have to face and of course iOS provides solution for that, well, sort of. The problem is: SCNetworkReachability (in SystemConfiguration framework) is implemented as low level C APIs and not so easy to use. Fortunately RestKit is bundled with a very straight-forward Objective-C wrapper for that, the RKReachabilityObserver.

When RKClient (or RKObjectManager) is initialized with some base URL, RestKit automatically initializes an instance of RKReachabilityObserver targeted at the host specified in the base URL and can be accessed via baseURLReachabilityObserver property. This observer also automatically registers RKReachabilityStateChangedNotification events in the default notification center. So in most cases we can use it in very simple way:

- (id)init {
    if ((self = [super init])) {
        [[NSNotificationCenter defaultCenter] addObserver:self 
            selector:@selector(reachabilityChanged:) 
            name:RKReachabilityStateChangedNotification 
            object:nil];
    }
 
    return self;
}
 
- (void)reachabilityChanged:(NSNotification*)notification {
    RKReachabilityObserver *observer = (RKReachabilityObserver *)[notification object];

    if ([observer isNetworkAvailable]) {
        NSLog(@"We're online!");

        if (RKReachabilityReachableViaWiFi == [observer networkStatus]) {
            NSLog(@"…via WiFi.");
        } 
        else if (RKReachabilityReachableViaWWAN == [observer networkStatus]) {
            NSLog(@"…via mobile network.");
        }
    }
    else if ([observer isConnectionRequired]) {
        NSLog(@"Mobile network may be available if we open a connection...");
    } 
    else {
        NSLog(@"We're offline!");
    }
}

Sending Multi-part Data

Sending multi-part data through HTTP requests is very common requirement and incredibly difficult in most programming languages. RestKit uses RKParams and RKParamsAttachment to handle that problem. RKParams is the container that can hold any kinds of parameters. For simple data type it works just like a NSDictionary, and for multi-part data it can wrap NSData and/or RKParamsAttachment objects. Both RKParams and RKParamsAttachment are MIME type friendly. The following self-explained code sample shows all:

NSString* filePath = @"/path/to/avatar.jpg";
RKParams* params = [RKParams params];
 
[params setValue:@"Neo" forParam:@"name"];
[params setValue:@"soulhacker@matrix.net" forParam:@"email"];
 
RKParamsAttachment* attachment = [params setFile:filePath forParam:@"avatar"];
attachment.MIMEType = @"image/jpeg";
attachment.fileName = @"avatar.jpg";
 
UIImage* image = [UIImage imageNamed:@"wallpaper.png"];
NSData* imageData = UIImagePNGRepresentation(image);
[params setData:imageData MIMEType:@"image/png" forParam:@"wallpaper"];
 
NSLog(@"ContentType = %@, ContentLength = %@", 
    [params HTTPHeaderValueForContentType],
    [params HTTPHeaderValueForContentLength]);
 
// Send a Request!
[[RKClient sharedClient] post:@"/upload" params:params delegate:self];

Background Upload/Download

iOS supported multi-tasking since 4.0 but it’s a highly restricted support and there are so many misconceptions among the users and even developers. I would strongly suggest Fraser Speirs’ excellent blog which explains all about iOS multi-tasking.

RestKit can seamlessly facilitate multi-tasking to prevent important long-time requests being interrupted by user switching out of the app. The key is the backgroundPolicy property of RKRequest which can be one of 4 enum values:

  • RKRequestBackgroundPolicyNone: the default value, do nothing for backgrounding;
  • RKRequestBackgroundPolicyCancel: cancel the request when app switches to background;
  • RKRequestBackgroundPolicyContinue: continue the request in background;
  • RKRequestBackgroundPolicyRequeue: place the request back to the queue for next activation.

So to make a request continue to work in background, just set its backgroundPolicy property as below:

RKRequest* request = [[RKClient sharedClient] post:@"/upload" delegate:self];
request.backgroundPolicy = RKRequestBackgroundPolicyContinue;

Conclusion

RestKit is a clean and elegant solution for iOS app to interact with RESTful web services. It encapsulates many everyday work into a tiny yet powerful framework and extremely easy to use. Suggest every iOS developers to give it a try.

References


  1. In fact ASIHTTPRequest has been abandoned since September 2011, and RestKit is one of replacements its author suggested. ↩︎

  2. The JSON parsing layer is pluggable, default to JSONKit. And I haven’t mentioned the old XML, have I? ↩︎

  3. Created by Jeff Verkoeyen, the author of Nimbus, the highly improved successor of Facebook’s Three20. ↩︎