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:
- 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 supportsGET
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. - A powerful but elegant object mapping system, which uses idiomatic key-value coding mechanism to define and implement the round-trip transformation between
JSON
2 string and native Objective-C class. Well, the magic behind it is called SOCKit3. - 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.
- 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:
- Analyze the business and make abstraction.
- Design the RESTful API spec based on the first step, including all call endpoints, parameters and returning data in JSON format.
- 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:
|
|
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:
|
|
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
- RestKit References (master branch)
- Tutorial: Introduction to RestKit
- Advanced RestKit Development
- RestKit Solution Recipes
-
In fact ASIHTTPRequest has been abandoned since September 2011, and RestKit is one of replacements its author suggested. ↩︎
-
The JSON parsing layer is pluggable, default to JSONKit. And I haven’t mentioned the old XML, have I? ↩︎
-
Created by Jeff Verkoeyen, the author of Nimbus, the highly improved successor of Facebook’s Three20. ↩︎