RestKit is a powerful library that simplifies interacting with web services for iOS applications. In this article, written by RestKit creator and Two Toasters CTO Blake Watters, we will take a quick tour of RestKit’s feature set, get familiar with the core concepts presented by the library, and then explore some code samples to get a feel for what working with RestKit is really like.
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’. RestKit’s primary goal is to allow the developer to think more in terms of their application’s data model and worry less about the details of sending requests, parsing responses, and building representations of remote resources.
RestKit is available as a downloadable binary package, as a versioned snapshot, or as a Git submodule if you wish to track mainline development. For users of the library uninterested in doing development, we recommend using the versioned binary packages for the simplicity of installation. If you wish to install as a submodule or build the library yourself, please refer to the documentation available on Github.
You can install RestKit in a few easy steps:
Close out the inspector window.
Congratulations, you are now done adding RestKit into your project!
You now only need to add includes for the RestKit libraries at the appropriate places in your application. The relevant includes are:
#import <RestKit/RestKit.h> #import <RestKit/CoreData/CoreData.h>// If you are using Core Data…
Build the project to ensure everything is working correctly.
Once you have verified that you have RestKit linked into your project correctly, you are ready to begin using the library.
RestKit is designed to make common tasks as straightforward and simple as possible. In this section we will run through many common tasks in the library and focus on code samples to help you get started with the library.
All of RestKit’s higher level functionality is built on top of the network layer. The network layer’s primary responsibility is the construction and dispatch of requests and the processing of responses. Generally you will dispatch all requests through the RKClient class.
RKClient is a web client object configured to talk to a particular web server. It is initialized with a base URL and allows you to set configuration that is common to the requests in your application, such as HTTP headers and authentication information. While you are free to initialize as many instances of RKClient as is appropriate for your application, there is a shared singleton instance that is globally available. This singleton instance is often configured in your app delegate’s applicationDidFinishLaunching:withOptions:
method:
- (void)applicationDidFinishLaunching:(UIApplication*)application withOptions:(NSDictionary*)options { RKClient* client = [RKClient clientWithBaseURL:@"http://restkit.org"]; }
The first RKClient that is initialized is automatically configured as the singleton instance and becomes available via the sharedClient singleton method:
NSLog(@"I am your RKClient singleton : %@", [RKClient sharedClient]);
Now that you have a client configured, you can send and process HTTP requests through the client. RestKit makes this very easy for you and abstracts the low level details of NSURLConnection away from you. When making a request through the client, you supply the resource path on the remote web server that you wish to interact with. Since the most common action in an iOS application is making an asynchronous request to a remote web service, RestKit provides very straight-forward convenience methods for the HTTP verbs: GET, POST, PUT and DELETE. You only need to declare that your class implements the RKRequestDelegate protocol and then provide an implementation of the request:didLoadResponse:
method. Let’s take a look at an example class that shows the basics:
#import <RestKit/RestKit.h> // Here we declare that we implement the RKRequestDelegate protocol // Check out RestKit/Network/RKRequest.h for additional delegate methods // that are available. @interface RKRequestExamples : NSObject <RKRequestDelegate> { } @end @implementation RKRequestExamples - (void)sendRequests { // Perform a simple HTTP GET and call me back with the results [[RKClient sharedClient] get:@"/foo.xml" delegate:self]; // Send a POST to a remote resource. The dictionary will be transparently // converted into a URL encoded representation and sent along as the request body NSDictionary* params = [NSDictionary dictionaryWithObject:@"RestKit" forKey:@"Sender"]; [[RKClient sharedClient] post:@"/other.json" params:params delegate:self]; // DELETE a remote resource from the server [[RKClient client] delete:@"/missing_resource.txt" delegate:self]; } - (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response { if ([request isGET]) { // Handling GET /foo.xml if ([response isOK]) { // Success! Let's take a look at the data NSLog(@"Retrieved XML: %@", [response bodyAsString]); } } else if ([request isPOST]) { // Handling POST /other.json if ([response isJSON]) { NSLog(@"Got a JSON response back from our POST!"); } } else if ([request isDELETE]) { // Handling DELETE /missing_resource.txt if ([response isNotFound]) { NSLog(@"The resource path '%@' was not found.", [request resourcePath]); } } } @end
As you can see, the code is extremely succinct and readable. There are a number of helper methods available on RKRequest and RKResponse that make inspecting your request state very easy. Be sure to read the headers and get familiar with what’s available.
Sending and receiving HTTP requests with such ease is great and all, but that’s just the tip of the iceberg. RestKit’s real power comes not from the network layer, but from the object mapping layer that sits on top of it. Object mapping is RestKit’s solution to simplifying and DRYing up the overly verbose work-flow of:
Much as RKClient is your gateway to a simpler life with HTTP, RKObjectManager is your gateway to the world of object mapping. In fact, on projects where object mapping is used extensively you will initialize RKObjectManager instead of RKClient. Much as RKClient seeks to abstract away the gritty details of handling requests, RKObjectManager works hard to shield you from the complexities of transforming data payloads into objects.
Object mapping requires that you provide a data model class to represent your remote objects. By implementing the RKObjectMappable protocol, you are configuring RestKit to map attributes within a retrieved payload to properties on your model class. The key to this process is the elementToPropertyMappings
method, which defines a dictionary of key paths and property names. The key paths are key-value coding compliant strings for accessing data within a parsed document. The property name is simply the string name of a property on the class to assign the accessed data to.
To illustrate these points, let’s imagine that our application has a lightweight contact concept containing a name, an e-mail address, and an identifier number. Let’s imagine that this record lives on our remote server at /contacts/1234
. The JSON looks like this:
{'id': 1234, 'name': 'Blake Watters', 'company': 'Two Toasters'}
Let’s pull together an RKObject class to contain this data:
@interface Contact : RKObject { NSNumber* _identifier; NSString* _name; NSString* _company; } @property (nonatomic, retain) NSNumber* identifier; @property (nonatomic, retain) NSString* name; @property (nonatomic, retain) NSString* company; @end
Now we just need to tell RestKit how to map data from the payload to our properties:
@implementation Contact + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: @"id", @"identifier", @"name", @"name", @"company", @"company", nil]; } @end
We are now all set to load the data. To do this, we set up RKObjectManager and execute a GET on the record. RKObjectManager will construct and configure an asynchronous RKObjectLoader request for you and send it to the remote server for processing. Instead of implementing the low level RKRequestDelegate methods that deal with requests and responses, we will instead implement the RKObjectLoaderDelegate protocol and get called back with a collection of mapped objects or an error. Let’s take a look at this code:
- (void)loadContact { RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:@"http://restkit.org"]; [manager loadObjectsAtResourcePath:@"/contacts/1" objectClass:[Contact class] delegate:self] } // RKObjectLoaderDelegate methods - (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects { Contact* contact = [objects objectAtIndex:0]; NSLog(@"Loaded Contact ID #%@ -> Name: %@, Company: %@", contact.id, contact.name, contact.company); } - (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error { NSLog(@"Encountered an error: %@", error) }
As you can see, the entire process is very low ceremony and completely DRY.
Loading objects is only half the story. To really interact with a remote web service, you also need to be able to create, update, and delete remote object instances. A confounding factor in these interactions is often that the resource path that an object resides at is specific to each instance. Returning to the contacts example above, imagine that the entire world of Contacts is represented by the following pairs of HTTP verbs and resource paths:
GET /contacts
returns all Contacts as a collectionPOST /contacts
creates a new ContactGET /contacts/<id>
returns a particular Contact detailsPUT /contacts/<id>
updates an existing Contact detailsDELETE /contacts/<id>
deletes an existing ContactTo avoid littering code with these conventions and resource paths, RestKit offers a routing system that is capable of generating resource paths for an object. Routing is designed to be an extensible system to provide flexibility, but RestKit ships with a very capable implementation in the RKDynamicRouter class. Routing is enabled by assigning an instance of an object implementing the RKRouter protocol to the RKObjectManager and configuring the router appropriately. Let’s take a look at an example configuration using RKDynamicRouter and our Contact example:
RKDynamicRouter* router = [RKDynamicRouter new]; // Define a default resource path for all unspecified HTTP verbs [router routeClass:[Contact class] toResourcePath:@"/contacts/(identifier)"]; [router routeClass:[Contact class] toResourcePath:@"/contacts" forMethod:RKRequestMethodPOST]; [RKObjectManager sharedManager].router = router;
The notable piece in the configuration is the use of parentheses in the resource path for the default route. Within the parentheses you can specify any instance method on the class being configured and when RestKit generates a resource path for that object, the value returned will be interpolated into the string.
In our example above, we can see that GET, PUT, and DELETE operations will generate /contacts/1234 while POST will generate /contacts.
Now that we have configured routing, we can manipulate remote object representations at a very high level. Let’s take a look at some more code and then we’ll walk through the process:
- (void)createObject { Contact* joeBlow = [Contact object]; joeBlow.name = @"Joe Blow"; joeBlow.company = @"Two Toasters"; // POST to /contacts [[RKObjectManager sharedManager] postObject:joeBlow delegate:self]; } - (void)updateObject { Contact* blake = [Contact object]; blake.identifier = [NSNumber numberWithInt:1]; blake.name = @"Blake Watters"; blake.company = @"RestKit"; // PUT to /contacts/1 [[RKObjectManager sharedManager] putObject:blake delegate:self]; } - (void)deleteObject { Contact* blake = [Contact object]; blake.identififer = [NSNumber numberWithInt:1]; // DELETE to /contacts/1 [[RKObjectManager sharedManager] deleteObject:blake delegate:self]; }
What we have done here is used the combined power of object mapping and routing to perform very high level manipulations on local and remote objects. Behind the scenes, RestKit has identified the appropriate resource path for your operation, created and dispatched an asynchronous request, and processed the response for you.
Client and Object Manager. There are two primary entry points for working with RestKit in your application: RKClient and RKObjectManager. RKClient is the primary entry point when you are working with the Network layer of RestKit and concerns itself with the low level details of building and sending requests. RKObjectManager operates at a higher level of abstraction in the Object Mapping layer and concerns itself with the loading and manipulation of objects that represent remote resources. Depending on what you are trying to accomplish with RestKit, you will be working extensively with one (or both!) of these classes.
Base URL’s and Resource Paths. RestKit uses the concepts of the ‘Base URL’ and ‘Resource Path’ to coordinate access to remote object representations. The Base URL is simply the common part of all URL’s to your remote application and is used to initialize instances of the RKClient and RKObjectManager classes. A resource path is simply the path (or subpath) portion of the full URL to an HTTP resource. Given an RKClient object initialized with ‘http://restkit.org’ and a request to GET the content at resource path ‘/foo/bar.json’, RestKit will create and send a request to ‘http://restkit.org/foo/bar.json’. This allows you to easily support development, staging, and production environments in your applications by conditionally compiling different base URL’s. Most of the time you will think entirely in terms of resource paths once you have moved beyond initializing the library.
Example:
RKClient* client = [RKClient clientWithBaseURL:@"http:///restkit.org"]; [client get:@"/foo/bar.json" delegate:self];
Example:
@implementation MyObject // Map full_name and street_adddress in JSON payload to // local properties fullName and streetAddress + (NSDictionary*)elementToPropertyMappings { return [NSDictionary dictionaryWithKeysAndObjects: @"full_name", @"fullName", @"street_address", @"streetAddress", nil]; } @end
Example:
RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:@"http://restkit.org"]; RKDynamicRouter* router = [[RKDynamicRouter new] autorelease]; manager.router = router; // Send POST requests for instances of Article to '/articles' [router routeClass:[Article class] toResourcePath:@"/articles" forMethod:RKRequestMethodPOST]; // Configure a default resource path for Articles. Will send GET, PUT, and DELETE requests to '/articles/XXXX' // articleID is a property on the Article class [router routeClass:[Article class] toResourcePath:@"/articles/(articleID)"]; // Configure Comments on the Article. Send POST of Comment objects to '/articles/1234/comments' // where the Comment has a relationship to an Article. [router routeClass:[Comment class] toResourcePath:@"/articles/(article.articleID)/comments" forMethod:RKRequestMethodPOST]; // Let's create an Article Article* article = [Article object]; article.title = @"Foo"; article.body = @"This is the body"; // Send a POST to /articles to create the remote instance [[RKObjectManager sharedManager] postObject:article delegate:self]; // Now let's create a Comment on the Article Comment* comment = [Comment object]; comment.article = article; comment.body = @"This is the comment!"; // Given Article has an ID of 1234, will send a POST to /articles/1234/comments to create the Comment [[RKObjectManager sharedManager] postObject:comment delegate:self]; // Delete the Article. DELETE to /articles/1234 [[RKObjectManager sharedManager] deleteObject:comment delegate:self];
This article has explored the basics of working with RestKit. You should now have a firm understanding of the core concepts and feel well prepared to start building your next RESTful iOS app. As we mentioned in the introductory section, RestKit also includes some advanced features which were not explored in this article. We will fully explore the advanced portions of the library including Core Data in our upcoming advanced tutorial. Until then, you can learn more through the example code included with the library and by exploring the resources below.