Getting Acquainted with RestKit (Up to version v0.10)

rjmunro edited this page Jan 7, 2013 · 2 revisions

Introduction to RestKit

In this article we will take a look at RestKit, a powerful library for iOS applications that simplifies interacting with web services. 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 flavor for what working with RestKit is really like.

What is 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'. 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.

What does RestKit provide?

  • A simple, high level HTTP request/response system. RestKit ships with an HTTP client built on top of NSURLConnection and provides a library of helpful methods for inspecting MIME types and status codes. Submitting form data is as simple as providing a dictionary of parameters and a native params object is included for easily creating multi-part submissions. Simple support for the streaming upload of large files (e.g., videos) is also provided. Requests can be grouped together into queues to limit consumption of network resources and responses can be cached to avoid network loads all together. A high level wrapper around the System Configuration Reachability APIs is also provided and is integrated into the HTTP client and object management components.
  • Framework level support for switching servers & environments (development/production/staging). RestKit uses a base URL and resource paths rather than full URLs to allow you to switch target servers quickly. Interpolating URL strings and constructing NSURL objects is a thing of the past.
  • An object mapping system. RestKit provides a modeling layer for mapping processed data payloads into native Cocoa objects declaratively. This lets the application programmer stop worrying about parsing and simply ask the framework to asynchronously fetch a remote resource and call the delegate with the results. Object mapping is implemented using key-value coding, allowing for quick traversal of the parsed object graph. Reflection is used on the property types to allow for mapping from values that don't have direct representations in the encoding format, such as mapping JSON timestamps encoded as a string to NSDate objects.
  • Core Data support. Building on top of the object mapping layer, RestKit provides integration with Apple's Core Data framework. This support allows RestKit to persist remotely loaded objects directly back into a local store, either as a fast local cache or a primary data store that is periodically synced with the cloud. RestKit can populate Core Data associations for you, allowing natural property based traversal of your data model. It also provides a nice API on top of the Core Data primitives that simplifies configuration and querying use cases through an implementation of the Active Record access pattern.
  • Database seeding. When the Core Data object store is used, you can seed a database from a collection of data files. This lets you submit your apps to the App Store with a database in the app bundle that is ready for immediate use.
  • Pluggable parsing layer. RestKit currently supports JSON via the SBJSON and YAJL parsers. Parsing is implemented behind a simple interface to allow additional data formats to be handled transparently.

Getting Up and Running

RestKit is available as a versioned source snapshot or as a Git submodule if you wish to track development. RestKit development moves quickly and we recommend using a Git submodule workflow.

RestKit currently requires iOS 4.0 or higher and is only supported for Xcode 4.x. Installation is covered in detail on Installing RestKit in Xcode 4.x wiki page.

Using RestKit

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.

Sending requests & processing responses

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. You can find a full reference for the properties and methods available on instances of RKClient on RKClient API Docs.

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. There are currently 2 authentication techniques supported: HTTP Authentication Support on RestKit and OAuth Support on RestKit (which includes OAuth1 and OAuth2). 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:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [RKClient clientWithBaseURLString:@"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 sharedClient] 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

There are numerous delegate methods exposed by in the RKRequestDelegate protocol and a number of helper methods available on RKRequest and RKResponse to streamline common tasks. Please refer to the API docs for RKRequestDelegate, RKRequest, and RKResponse for more information.

An Introduction to Object Mapping

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:

  1. Sending a request to a remote web service
  2. Getting back an XML or JSON response and parsing it
  3. Taking the parsed response and assigning the values inside the payload to objects

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. RKObjectManager is documented in the API docs as well.

Object mapping is built around the key-value coding informal protocol that is used throughout the Cocoa ecosystem on OS X and iOS. You need to have a complete understanding of key-value coding to understand RestKit's object mapping engine. Apple provides extensive documentation about key-value coding in the Key-Value Coding Programming Guide.

Modeling & Loading Remote Objects

Object mapping requires that you provide a data model class to represent your remote objects. RestKit can map remote payloads back into any object that is key-value coding compliant. This typically means descendent classes of NSObject or NSManagedObject. We'll examine in the simple case now and return to the specifics of Core Data managed objects later. To perform an object mapping operation, RestKit requires that you configure an instance of RKObjectMapping to instruct the mapper on how to handle your data. An RKObjectMapping can be thought of a template for how to perform a transformation from one representation of an object into another. It relies on key-value coding and run-time type introspection to determine how to transform data between objects. All object mappings are comprised of a source and a destination key path. The source key path is a key-value coding string for accessing a particular object within a parsed XML or JSON document. The destination key path is a key-value coding string identifying the property on your model class where you want the mapped value to be assigned.

To illustrate these points, let's imagine that our application has a lightweight contact concept containing a name, company name, 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 NSObject class to model this data:

    @interface Contact : NSObject

    @property (nonatomic, strong) NSNumber* identifier;
    @property (nonatomic, copy) NSString*   name;
    @property (nonatomic, copy) NSString*   company;

    @end
    
    @implementation Contact

    @end

Now we just need to tell RestKit how to map data from the payload into our object by defining an object mapping:

    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[Contact class]];
    [objectMapping mapKeyPath:@"id" toAttribute:@"identifier"];
    [objectMapping mapKeyPath:@"name" toAttribute:@"name"];
    [objectMapping mapKeyPath:@"company" toAttribute:@"company"];

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 {
        RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[Contact class]];
        [objectMapping mapKeyPath:@"id" toAttribute:@"identifier"];
        [objectMapping mapKeyPath:@"name" toAttribute:@"name"];
        [objectMapping mapKeyPath:@"company" toAttribute:@"company"];
        RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURLString:@"http://restkit.org"];
        [manager loadObjectsAtResourcePath:@"/contacts/1" objectMapping:objectMapping delegate:self];
    }

    // RKObjectLoaderDelegate methods

    - (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
        Contact *contact = objects[0];
        NSLog(@"Loaded Contact ID #%@ -> Name: %@, Company: %@", contact.identifier, contact.name, contact.company);
    }

    - (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
        NSLog(@"Encountered an error: %@", error);
    }

So what just happened here? Let's break it down:

  1. We configured an object mapping defining how to transform keys in the JSON to properties on our model.
  2. We created an instance of RKObjectManager and asked it to asynchronously load data for us from the remote web service at /contacts/1
  3. The web server responded with a 200 response and returned our example JSON above.
  4. On a background thread, RestKit instantiated a JSON parser (based on how the choices you made during installation) and parsed the payload.
  5. RestKit then instantiated an instance of RKObjectMapper and configured it to use the RKObjectMapping we provided to the invocation of loadObjectsAtResourcePath:objectMapping:delegate:.
  6. The object mapper then inspected the payload and mapped each key in the JSON into the appropriate attribute on our Contact class.
  7. Once finished, the results were bundled up and sent back to our delegate via the objectLoader:didLoadObjects: method.

This completes a quick object mapping tour. There's a great deal more functionality available than what we just explored, but the basic foundation is always the same. For a detailed look at object mapping and what else is available, please consult the detailed Object Mapping guide on the RestKit wiki.

Setting Up Routes

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 collection
  • POST /contacts creates a new Contact
  • GET /contacts/<id> returns a particular Contact details
  • PUT /contacts/<id> updates an existing Contact details
  • DELETE /contacts/<id> deletes an existing Contact

To 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 also based on key-value coding and works through registering simple string patterns that are interpolated at run time. Routing is configured through the RKObjectRouter class and every RKObjectManager has an instance of the router available via the router property. Let's take a look at how to configure routing for our Contact class:

    // Grab the reference to the router from the manager
    RKObjectRouter *router = [RKObjectManager sharedManager].router;
    
    // 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];

The notable piece in the configuration is the the use of a colon in the resource path for the default route. Following the colon, 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. This is a key-value coding key path embedded within the string. You can traverse relationships using the dot notation as well as specifying simple attribute access.

In our example above, we have defined a default route of @"/contacts/:identifier" which will be used for all HTTP verbs (GET, POST, PUT and DELETE) and then registered a specific route for the POST verb to /contacts. Using our Contact example above, this means that our resource path will be /contacts/1234 for GET, PUT, and DELETE, and POST operations will be sent to /contacts.

Manipulating Remote Objects

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 = @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.identifier = @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, create and dispatched an asynchronous request, and processed the response for you.

Review of Key Concepts

  • 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];
  • Object Mapping. When you use RestKit to model remote resources into local objects, you will be interacting with the object mapping layer. Object mapping is the process of taking a remote JSON (or other wire format) payload, parsing it into a graph of key value coding compliant NSObject's, and applying a set of mapping rules to transform values inside the parsed object graph into attributes on a model object. RestKit supports advanced mapping beyond what you get from simply decoding a payload, such as parsing a string containing a date into an NSDate property and accessing data via key-value coding operators. Object mapping rules are configured by creating instances of the RKObjectMapping and defining transformations between key paths in a source and destination object.

Example:

    RKObjectMapping *objectMapping = [RKObjectMapping mappingForClass:[Contact class]];
    [objectMapping mapKeyPath:@"id" toAttribute:@"identifier"];
    [objectMapping mapKeyPath:@"name" toAttribute:@"name"];
    [objectMapping mapKeyPath:@"company" toAttribute:@"company"];
  • Routing. The routing system is responsible for generating resource paths from local object instances. This allows you to manipulate and synchronize your local objects with their remote representations without ever seeing a URL. The routing system is extensible by providing your own implementation of the RKRouter protocol, but RestKit ships with a powerful implementation in the RKDynamicRouter class. The dynamic router allows you to encoded property names inside of simple strings to generate complex resource paths at run time. This is most easily understood through some examples:

Example:

    RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:@"http://restkit.org"];
    
    // Send POST requests for instances of Article to '/articles'
    [manager.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
    [manager.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.
    [manager.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:article delegate:self];

Conclusion

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.

Learning More