Skip to content

Object Mapping

mozeryansky edited this page Jun 10, 2015 · 57 revisions

This document details the design of object mapping in RestKit as of version 0.21.0

Object Mapping Overview

RestKit is an integrated solution for implementing web service API clients on iOS and Mac OS X. The library includes simple, elegant networking operations provided by AFNetworking and an object mapping system for modeling object representations retrieved over HTTP. Object mapping is the process of taking a representation of data in one form and transforming it into another. RestKit's object mapping engine is a generalized system for performing such transformations and is usable as a standalone library. RestKit also provides integrated networking operations that provide support for retrieving resources via HTTP and performing mapping on the responses, as well as mapping from local domain objects into request bodies for sending requests to a remote backend system. This document provides a detailed overview of the mapping engine.

Key-Value Coding

Object mapping is built on top of the key-value coding pattern that permeates the Cocoa frameworks. KVC is a mechanism for expressing read and write operations on an object graph in terms of simple strings. RestKit relies on KVC to identify mappable content within a parsed response body and dynamically update the attributes and relationships of your local domain objects with the appropriate content. An understanding of key-value coding is essential to fully understand and leverage the capabilities of the RestKit framework. Before diving into the details of RestKit's object mapping system, be sure to get a firm grasp on KVC. The best way to get oriented with KVC is through a close reading of Apple's Key-Value Coding Programming Guide.

Object Mapping by Example

To understand the object mapping subsystem of RestKit, let's consider an example. Imagine that we are building an app that loads a collection of news articles from a remote system. Each article has a title, a body, an author, and a publication date. We expect our JSON to come back something like this:

{ "articles": [
    { "title": "RestKit Object Mapping Intro",
      "body": "This article details how to use RestKit object mapping...",
      "author": "Blake Watters",
      "publication_date": "7/4/2011"
    },
    { "title": "RestKit 1.0 Released",
      "body": "RestKit 1.0 has been released to much fanfare across the galaxy...",
      "author": "Blake Watters",
      "publication_date": "9/4/2011"
    }]
}

Within our iOS application, we are going to have a table view showing the same information. We have an Objective-C class to hold this data that looks like the following:

@interface Article : NSObject
    @property (nonatomic, copy) NSString* title;
    @property (nonatomic, copy) NSString* body;
    @property (nonatomic, copy) NSString* author;
    @property (nonatomic) NSDate*   publicationDate;
@end

Our goal is to leverage RestKit's object mapping capabilities to turn the above JSON into an array of Article instances. To make this happen, we must first become familiar with a few RestKit classes:

  1. RKObjectMapping: An object mapping defines the rules for transforming an object from one representation into another. Each object mapping is composed of a collection of property mappings, expressing desired transformations in terms of key paths. Property mappings define how to transform values into attributes and relationships on the destination object.
  2. RKResponseDescriptor: A response descriptor defines if a given object mapping matches an HTTP response. Matches are evaluated by comparison of the URL from which the response was loaded, by key-path from within the deserialized response body, or both.
  3. RKObjectRequestOperation: An object request operation is responsible for managing the life-cycle of an object mapped HTTP request from start to finish. It handles the transmission of the HTTP request, deserialization of the response, and processing of the response through the object mapping engine. Object request operations are configured with an NSURLRequest and one or more RKResponseDescriptor objects specifying ways in which the response may be mapped.

Let's take a look at how we would configure RestKit to perform this operation:

RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping addAttributeMappingsFromDictionary:@{ 
    @"title": @"title",
    @"body": @"body",
    @"author": @"author",
    @"publication_date": @"publicationDate"
}];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping method:RKRequestMethodAny pathPattern:nil keyPath:@"articles" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

Let's consider what we've done here. In the first line, we created an instance of RKObjectMapping defining a mapping for the Article class. We then configured the mapping to define rules for transforming data within the parsed payload to attributes on an instance of Article. Finally, we constructed a response descriptor specifying that the articleMapping is to be used whenever a response is encountered in the 2xx (Successful) status code range and the deserialized response document contains an object representation or array of object representations at the @"articles" key path.

Now that we have configured our object mapping and response descriptor, we can load this collection using an object request operation:

- (void)loadArticles
{
    RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
    [articleMapping addAttributeMappingsFromDictionary:@{ 
        @"title": @"title",
        @"body": @"body",
        @"author": @"author",
        @"publication_date": @"publicationDate"
    }];

    RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping method:RKRequestMethodAny pathPattern:nil keyPath:@"articles" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
    
    NSURL *URL = [NSURL URLWithString:@"http://restkit.org/articles"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
    [objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        RKLogInfo(@"Load collection of Articles: %@", mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        RKLogError(@"Operation failed with error: %@", error);
    }];
    
    [objectRequestOperation start];
}

Let's walk through what we've just done during the configuration of this operation:

  1. We configured an NSURLRequest specifying that we wish to load the resource at 'http://restkit.org/articles'.
  2. We then initialized an RKObjectRequestOperation with the request object and the response descriptor previously configured, which contains the object mapping for the 'Article' class.
  3. We then configured a completion block on the object request operation, specifying two blocks for execution based on the outcome of the operation's execution. The success block is invoked when the HTTP request returns a mappable representation of the resource with a 2xx (Successful) status code and at least one response descriptor was found matching the request. The failure block is invoked in the event that the HTTP request fails, there is an error during the object mapping of the loaded resource, or if the remote system returned a mappable representation of an error (more on this later). In both the success and failure blocks we configured some simple logging to show us what happened.
  4. We started the operation to begin its execution. The request is started asynchronously and the success or failure blocks are invoked upon completion, informing us of the results.

Now, from an end-user perspective everything is done and ready to go. Provided that the configuration is correct and the server returns the expected JSON, we will see an array of Article objects logged to the console. But behind the scenes, a number of additional things have taken place that are worth exploring to truly understand how RestKit works its magic:

  1. The RKObjectRequestOperation object constructed an instance of RKHTTPRequestOperation with the NSURLRequest given. The HTTP request operation is a subclass of AFHTTPRequestOperation, the AFNetworking operation for generic HTTP requests.
  2. The HTTP request operation is started and run to completion, loading the desired resource via HTTP. If the HTTP operation fails to load a response, then the error property is set and the object request operation is considered failed, thus triggering the invocation of the failure completion block.
  3. If the network operation completes and a response is loaded, then the object request operation initializes an RKResponseMapperOperation to handle processing the response via the object mapping engine.
  4. The response mapper operation first evaluates the content type of the response to determine if a serialization implementation is available to deserialize the response body. Serialization implementations are pluggable in RestKit via the RKMIMETypeSerialization class. Out of the box support for the 'application/json' content type is provided by the RKNSJSONSerialization class, which integrates Apple's NSJSONSerialization class. If no serialization implementation is available to handle the response loaded, then the operation is failed with an unmappable representation error.
  5. If the response is successfully deserialized, then the response mapper operation searches the list of response descriptors to determine how to map the response. The list of response descriptors is first filtered by evaluating the URL from which the response was loaded against the path pattern of each response descriptor. A nil path pattern value indicates that the response descriptor matches any URL.
  6. Once the filtered set of response descriptors is constructed, the response mapper operation instantiates an instance of RKMapperOperation with the deserialized response body and a dictionary wherein the keys are equal to the key paths of the filtered response descriptors and the values are equal to the corresponding object mapping.
  7. The mapper operation is then started and evaluates each key path to mapping entry in turn. Recall the previous mention of the importance of key-value coding in the object mapping process. The mapper operation evaluates valueForKeyPath: against the deserialized response body. If a dictionary or array of dictionaries is found at the key path, then an instance of RKMappingOperation is created and started to process each dictionary at the identified key path. In our example loading a list of articles, this would mean that valueForKeyPath:@"articles" would be invoked and an array of dictionaries would be found. This array of dictionaries would then be iterated over and an instance of RKMappingOperation would be started for each entry in the array. For the example above, this means that we would generate two mapping operations:
// This dictionary will be processed in one mapping operation
{ "title": "RestKit Object Mapping Intro",
  "body": "This article details how to use RestKit object mapping...",
  "author": "Blake Watters",
  "publication_date": "7/4/2011"
}

// This dictionary will be processed in another mapping operation
{ "title": "RestKit 1.0 Released",
  "body": "RestKit 1.0 has been released to much fanfare across the galaxy...",
  "author": "Blake Watters",
  "publication_date": "9/4/2011"
}
  1. Once the object mapping operation takes over, a new set of KVC key paths is examined. The attribute mappings we configured via the call to addAttibuteMappingsFromDictionary: are now evaluated against the dictionary. RestKit will invoke valueForKeyPath:@"title", valueForKeyPath:@"body", valueForKeyPath:@"author", and valueForKeyPath:@"publication_date" against the dictionary to determine if there is any data available for mapping. If any data is found, it will set the data on the target object by invoking setValue:forKeyPath:. In the above example, RestKit will find the data for the title via valueForKeyPath:@"title" and then set the title attribute of our Article object to "RestKit Object Mapping Intro" and "RestKit 1.0 Released", respectively. This process is repeated for all the attributes and relationships defined in the object mapping. It is worth noting that although the key paths are often symmetrical between the source and destination objects (e.g., mapping a title to a title), they do not have to be and you can store your data in more logical or idiomatic names as appropriate (e.g., we mapped publication_date to publicationDate so that it fits better with Cocoa naming conventions).
  2. Once all mapping operations are completed, the RKMapperOperation returns an RKMappingResult to the response mapper. This mapping result is then bubbled back up to the RKObjectRequestOperation, which sees that the mapping was successful and executes the success completion block.

From this example, it should now be clear that object mapping can be thought of as a declarative, key-value coding chainsaw for your JSON data. We have declared that any time data is found underneath the @"articles" keyPath, it should be processed using the articleMapping and thus transformed into one or more instances of the Article class. Once mappable data is found, we have declared that values existing at a given source key path should be assigned to the target object at the destination key path. This is the fundamental trick of object mapping and all other features are built upon this foundation.

Object Mapping Fundamentals

Now that we have established a foundation for the basics of object mapping, we can explore the remaining portions of the system. We'll examine various use-cases of object mapping in turn with brief discussion and code samples.

Value Transformation

One of the notable features of object mapping is that it infers a great deal of information about your intentions by leveraging the dynamic features of the Objective-C runtime. RestKit will examine the source and destination types of your attribute at mapping time and perform a variety of type transformations for you. This feature eliminates a great deal of glue code that you would otherwise have to write if you were assigning a parsed data structure to your object model manually.

Value Transformation is performed by a separate, but tightly integrated library called RKValueTransformers. RKValueTransformers provides a simple, generalized architecture for handling transformations that commonly occur as data is transformed between representations. The core of the system is a protocol called RKValueTransforming which can be adopted by any object that wishes to act as a value transformer.

RKValueTransformers is a "batteries included" library that ships with a rich set of transformers that covers the most common transformation tasks encountered when interacting with a RESTful web services API. The following table enumerates a number of available transformations that are built into RKValueTransformers and are thus automatically applied any time you define an attribute mapping:

Source Type Destination Type Discussion
NSString NSDate NSString values are mapped to NSDate properties via `NSFormatter` or `NSDateFormatter` transformers.
NSString NSURL NSString values are mapped to NSURL properties via
[NSURL URLWithString:(NSString*)value]
NSString NSDecimalNumber NSString values are mapped to NSDecimalNumber properties via
[NSDecimalNumber decimalNumberWithString:(NSString*)value]
NSString NSNumber NSString values are mapped to NSNumber properties via
[NSNumber numberWithDouble:[(NSString*)value doubleValue] ]
NSString containing YES, NO, true, false, t, f NSNumber NSString values containing a known boolean string are mapped to NSNumber properties via
[NSNumber numberWithBool:boolValueFromString]
NSSet NSArray NSSet values are mapped to NSArray properties via
[(NSSet*)value allObjects]
NSArray NSSet NSArray values are mapped to NSSet properties via
[NSSet setWithArray:value]
NSNumber NSDate NSNumber values are mapped to NSDate properties via
[NSDate dateWithTimeIntervalSince1970:[(NSNumber*)value intValue] ]
NSCFBoolean NSString Boolean literals true and false parsed from JSON are mapped to NSString properties as @"true" and @"false"
NSNull Anything NSNull entries (null in JSON) are mapped to nil for any destination property.
respondsToSelector:@selector(stringValue) NSString Any mappable value can be mapped to an NSString property if the object responds to the stringValue selector. This works for NSNumbers, etc.

All of these default transformers are described in detail in the README.md on the RKValueTransformers Github page.

Transforming Date and Time Representations

One of the most common types of value transformation encountered by an application that exchanges data in the JSON format is the conversion between date and time representations, as JSON does not have a native date type. RestKit and RKValueTransformers view this problem as a subset of the large value transformation landscape and thus handle it using the RKValueTransformers architecture.

When date and time data is transmitted via JSON it is typically represented either a numeric value (such as the number of seconds since the epoch) or encoded within a string in a standard format (such as ISO-8601). On the Cocoa side of things, date and time data is represented using the NSDate class and is converted between NSString and NSDate representations via the NSDateFormatter class.

To reconcile these facts and present a simple, uniform interface for value transformation, RKValueTransformers simply makes all NSDateFormatter objects conform to the RKValueTransforming protocol. This means that any interface that accepts an id<RKValueTransforming> input value will accept any NSDateFormatter object.

The default value transformer (available via [RKValueTransformer defaultValueTransformer]) contains several pre-configured NSDateFormatter instances and you are free to register more, remove the existing instances, or reorder them via the RKCompoundValueTransformer API's. An RKCompoundValueTransformer is simply a value transformer that encapsulated an arbitrary number of underlying value transformers and attempts to transformer an input value using each one in turn. This means that any NSString <-> NSDate value transformer registered with the default transformer will be given an opportunity to transform an input string into a date. If it fails, the next transformer in the stack is given a chance to try. This has the side-effect of ensuring that the NSDateFormatter instances (which are notoriously expensive to construct and mutate) used to process your data are cached across objet mapping operations.

While NSDateFormatter is integrated into the RKValueTransformers architecture and is available to use it is important to realize that it is internally considered only as a value transformer that has expressed the ability to handle transformations between NSDate and NSString representations. This means that you can implement your own NSString to NSDate transformation routines that do not use NSDateFormatter at all.

Customizing Value Transformation

In addition to the default value transformers, you can manually configure a transformer to perform any sort of transformation that your application may need. Each RKPropertyMapping (the abstract superclass of RKAttributeMapping and RKRelationshipMapping) declares a valueTransformer property. By default, this property is set to a copy of [RKValueTransformer defaultValueTransformer] but can be set to any object that conforms to the RKValueTransforming protocol.

To facilitate the easy creation of new value transformers RKValueTransformers includes the RKBlockValueTransformer class, which provides an interface for implementing a new value transformer using Objective-C blocks. Implementing a new transformer is simple: you only need to implement the transformation itself and optionally provide a validation routine, which lets RKValueTransformers know what type of data your transformer can work with.

Let's take a look at a simple example transformer implementation. The following code implements a new value transformer that transforms an NSString value into a new NSString representation in which all characters have been converted into uppercase:

RKValueTransformer *uppercaseStringTransformer = [RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class sourceClass, __unsafe_unretained Class destinationClass) {
        // We transform a `NSString` into another `NSString`
        return ([sourceClass isSubclassOfClass:[NSString class]] && [destinationClass isSubclassOfClass:[NSString class]]);
    } transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, Class outputValueClass, NSError *__autoreleasing *error) {
        // Validate the input and output
        RKValueTransformerTestInputValueIsKindOfClass(inputValue, [NSString class], error);
        RKValueTransformerTestOutputValueClassIsSubclassOfClass(outputValueClass, [NSString class], error);

        // Perform the transformation
        *outputValue = [(NSString *)inputValue uppercaseString];
        return YES;
    }];

This compact bit of code implements the RKValueTransforming protocol and constructs a value transformer that ensures that its input and output types are correct before proceeding with the transformation. This ensures that RKValueTransformers will never crash your application in the event it encounters data of an unexpected data while applying transformers.

Once a value transformer has been declared, it can be plugged directly into a specific RKPropertyMapping or registered as a default value transformer for use across your entire application:

// All our article titles are IN UPPERCASE
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[Article class]];
RKAttributeMapping *titleMapping = [RKAttributeMapping attributeMappingFromKeyPath:@"title" toKeyPath:@"title"];
titleMapping.valueTransformer = uppercaseStringTransformer;
[mapping addPropertyMapping:titleMapping]

// We want all our strings to be IN UPPERCASE
[[RKValueTransformer defaultValueTransformer] addValueTransformer:uppercaseStringTransformer];

Now, obviously the above example of transforming strings into uppercase strings is a bit contrived. More realistic implementations of value transformers include transforming strings and numbers into database specific types (such as a BSONObjectID for MongoDB) or into non-Foundation types (such as a CLLocation representations of geographic coordinate data), transforming between numeric values and enumerated types, or performing transparant encryption or compression during mapping. An example of RKValueTransformer from NSString to NS_ENUM can be found on the wiki at RKValueTransformer NS_ENUM Example

For a more concrete example of how to implement and work with a custom value transformer implementation take a look at RKCLLocationValueTransformer, which provides support for mapping to and from CLLocation objects that can be used with MapKit.

Relationships

In addition to mapping simple attributes, RestKit is also capable of mapping arbitrarily complex object graphs. Relationship mappings are configured very similarly to attribute mapping, but with one notable addition: they are initialized with an object mapping for the relationship. To understand this, let's extend our previous articles JSON to contain some nested relationship data:

{ "articles": [
    { "title": "RestKit Object Mapping Intro",
      "body": "This article details how to use RestKit object mapping...",
      "author": {
          "name": "Blake Watters",
          "email": "blake@restkit.org"
      },
      "publication_date": "7/4/2011"
    }]
}

Notice that we have changed the structure of the "author" field. Rather than being a simple string, it now contains a nested dictionary. We want to represent this nested dictionary as a new type in our object model -- the Author class. Let's pull together a data model for our author data:

@interface Author : NSObject
    @property (nonatomic, copy) NSString* name;
    @property (nonatomic, copy) NSString* email;
@end

We also need to change author property type in Article from NSString* to Author*:

@interface Article : NSObject
    @property (nonatomic, copy) NSString* title;
    @property (nonatomic, copy) NSString* body;
    @property (nonatomic) Author* author;
    @property (nonatomic) NSDate* publicationDate;
@end

Now we just need to configure RestKit to map the data appropriately. Let's extend our previous articleMapping to include the new author relationship:

// Create our new Author mapping
RKObjectMapping* authorMapping = [RKObjectMapping mappingForClass:[Author class] ];
// NOTE: When your source and destination key paths are symmetrical, you can use addAttributesFromArray: as a shortcut instead of addAttributesFromDictionary:
[authorMapping addAttributeMappingsFromArray:@[ @"name", @"email" ]];

// Now configure the Article mapping
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class] ];
[articleMapping addAttributeMappingsFromDictionary:@{
    @"title": @"title",
    @"body": @"body",
    @"publication_date": @"publicationDate"
}];

// Define the relationship mapping
[articleMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"author"
                                                                               toKeyPath:@"author"
                                                                             withMapping:authorMapping]];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping
                                                                                        method:RKRequestMethodAny
                                                                                   pathPattern:nil
                                                                                       keyPath:@"articles"
                                                                                   statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

That's all there is to it. RestKit is now configured to map the above JSON into an array of Article objects, each of which has a related Author object. The configuration is the same for a nested array of data -- if RestKit encounters an array at mapping time it will map each element in the array using the supplied mapping and assign the mapped collection back to the destination property.

Object Parameterization & Serialization

Until now we have been concerned with loading remote object representations into our applications and mapping them into local objects. RestKit also supports an object mapping powered mechanism for parameterizing and serializing local objects for submission back to your backend system for processing. This facility is provided by the RKObjectParameterization class. It is important to note that serialization is just another object mapping operation -- it leverages the same core engine that is used to map parsed objects into local domain objects. The fundamental difference is that the target output of a serialization operation is an NSMutableDictionary. The attributes and relationships of your local domain objects are mapped into an intermediate dictionary implementation so that they can then be run through an encoder to produce URL Form Encoded or JSON data to be sent in the body of the request.

Enough theory, let's take a look at how we configure object parameterization. When configuring parameterization, we turn to a new class: RKRequestDescriptor. Like its sister class, RKResponseDescriptor, request descriptors encapsulate configuration for when a mapping is to be used. Request descriptors are configured by specifying the object class for which they are to be used and an optional root key path. Let's consider some code exploring how they are configured:

// Configure a request mapping for our Article class. We want to send back title, body, and publicationDate
RKObjectMapping* articleRequestMapping = [RKObjectMapping requestMapping ]; // Shortcut for [RKObjectMapping mappingForClass:[NSMutableDictionary class] ]
[articleRequestMapping addAttributeMappingsFromArray:@[ @"title", @"body", @"publicationDate" ]];

// Now configure the request descriptor
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:articleRequestMapping objectClass:[Article class] rootKeyPath:@"article" method:RKRequestMethodAny];

We have now built an object mapping that can take our local Article objects and turn them back into NSMutableDictionary instances and we have constructed a request descriptor expressing how the mapping and our class are related. Life is good -- let's see how it comes into play in the app:

// Create a new Article and POST it to the server
Article* article = [Article new];
article.title = @"This is my new article!";
article.body = @"RestKit is pretty cool. This is kinda slick.";
[[RKObjectManager sharedManager] postObject:article path:@"/articles" parameters:nil success:nil failure:nil];

So how do these pieces connect, you may be asking? When we invoke postObject:path:parameters:success:failure:, we are asking the object message to create and enqueue an object request operation in which the given object has been parameterized (that is, object mapped into a dictionary of parameters) and set as the request body. Behind the scenes, there is quite a bit of magic to this process. Let's take a look:

  1. RKObjectManager creates an RKObjectRequestOperation or RKManagedObjectRequestOperation by invoking appropriateObjectRequestOperationWithObject:method:path:parameters:.
  2. Within appropriateObjectRequestOperationWithObject:method:path:parameters:, the object request operation is constructed. This first involves creating an NSURLRequest with which to instantiate the operation. The request is obtained by invoking requestWithObject:method:path:parameters:, which is responsible for constructing the NSURLRequest object and constructing its request body. To do so, the destination URL must be obtained. There are two distinct code paths available for producing the URL: the path argument or the router. If a path is provided, then it is concatenated with the baseURL by invoking [NSURL URLWithString:relativeToURL:]. If the path is given as nil, then the router is searched to obtain a path configured for the object and method given.
  3. Once the URL has been obtained, the parameters are determined. If the request method is anything other than RKRequestMethodGET or RKRequestMethodDELETE, then the requestDescriptors are searched for a match for the given object. If a request descriptor is found, then [RKObjectParameterization parametersWithObject:requestDescriptor:error:] is invoked to map the object into an NSMutableDictionary representation. The dictionary representation is then merged with the input parameters (if any) and passed to [RKObjectManager requestWithMethod:path:parameters:] to construct the request.
  4. Within requestWithMethod:path:parameters:, the parameters are serialized and added to the request. For a 'GET', 'HEAD', or 'DELETE' request, the parameters are URL encoded and appended to the URL. For all other HTTP methods (i.e. 'POST', 'PUT', and 'PATCH'), the parameters are serialized via the [RKMIMEType dataFromObject:MIMEType:error:] method. The serialization format is determined by the value of the requestSerializationMIMEType property of the object manager. Once the NSURLRequest object is constructed, control returns to appropriateObjectRequestOperationWithObject:method:path:parameters:.
  5. To create the object request operation, the method must first determine the appropriate type of operation to construct. This is determined by examining the type of the object and the set of RKResponseDescriptor objects registered with the manager. If the object is an instance of NSManagedObject or one of its subclasses or the set of request descriptors contains an RKEntityMapping, then an instance of RKManagedObjectRequestOperation is created, else an RKObjectRequestOperation is constructed.
  6. Once the object request operation has created and configured, control returns to postObject:path:parameters:success:failure:, which sets the completion block and enqueues the operation.

Similar variations of this process are repeated by all of the methods exposed by the object manager. Note that all of the higher level operations are built upon the more primitive public methods of the manager. This enables you to construct and configure operations very easily that don't exactly match the out of the box configurations.

Also keep in mind the previously note that objects are parameterized into an intermediary NSMutableDictionary representation. This enables the use of additional serialization formats (such as XML, Protocol Buffers, BSON, Msgpack, etc) provided that they are able to read and write dictionaries and arrays in and out of NSData containers. Such serialization implementations can be implemented by conforming to the RKSerialization protocol and implementing two methods, then registering the MIME type with the RKMIMETypeSerialization class.

Something else you may have noticed when configuring request mapping is that most of the time our request mappings are extremely similar to our response mappings -- except the source and destination key paths are reversed and the destination class is always NSMutableDictionary. RestKit understands and recognizes this relationship between our mappings and provides some extremely convenient shortcuts for configuring serialization:

// Our familiar articlesMapping from earlier
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class] ];
[articleMapping addAttributeMappingsFromArray:@[ @"title", @"body", @"author"] ];
[articleMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"publication_date" toKeyPath:@"publicationDate"]];

// Build a request mapping by inverting our response mapping. Includes attributes and relationships
RKObjectMapping* articleSerializationMapping = [articleMapping inverseMapping];
// You can customize the mapping here as necessary -- adding/removing mappings

Mapping without KVC

As should be obvious by now, RestKit is a big believer in KVC and offers a very seamless workflow if your JSON conforms to the patterns. But sadly this is not always the case -- many web API's return their JSON without any nesting attributes that can be used for mapping selection. In these cases you can still work with RestKit, you just have to rely upon the use of URL matching. Let's consider another example: Imagine that our weblog services returning articles works just as before, but the JSON output looks like this:

[
    { "title": "RestKit Object Mapping Intro",
      "body": "This article details how to use RestKit object mapping...",
      "author": {
          "name": "Blake Watters",
          "email": "blake@restkit.org"
      },
      "publication_date": "7/4/2011"
    }
]

We no longer have the outer @"articles" key path to identify our content and instead have a plain old fashioned array. We'll configure our mappings much the same, but make a change in the configuration of the response descriptor:

// Our familiar articlesMapping from earlier
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
[articleMapping addAttributeMappingsFromDictionary:@{ 
    @"title": @"title",
    @"body": @"body",
    @"author": @"author",
    @"publication_date": @"publicationDate"
}];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping method:RKRequestMethodAny pathPattern:@"/articles" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];

Rather than configuring the request descriptor by key path, we have specified a path pattern. This pattern is evaluated against the URL from which the response was loaded by stripping the baseURL string from the URL and then passing the remaining path into an instance of RKPathMatcher. The path matcher evaluates URL patterns to produce matches with patterns such as '/articles/:articleID', which would match against '/articles/1234' or '/articles/some-great-article'.

Mapping Values without Key Paths

Another somewhat common mapping case where RestKit's reliance on KVC breaks down is in the mapping of raw values that have no nesting key-path. Consider for example the following JSON document:

    { "user_ids": [1234, 5678] }

And the corresponding model:

@interface RKExampleUser : NSObject
@property (nonatomic) NSNumber *userID;
@end

In this case, our JSON contains an array in which each value within the array is a value that we wish to map to the userID attribute. In order to perform this mapping, we make use of a special value for the source key path: nil. Whenever the mapping engine encounters a source key path of nil, it interprets the mapping as specifying that the current value is to be mapped without traversing any further down the representation. Recall that RestKit interprets arrays as collections that are to be interated and each value mapped as a nested representation -- it is this behavior, when combined with the nil source key path, that allows us to perform the mapping:

RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKExampleUser class]];
[userMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:@"userID"]];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodAny pathPattern:nil keyPath:@"user_ids" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];

When the mapping is performed, RestKit will fetch the value at the key-path "users" (as declared in the response descriptor) and then attempt to perform a mapping for each value in the array. In this case, since each value is an NSNumber there is no key-path that could possibly work -- other than nil. Finding an attribute mapping with a nil source key-path, RestKit will directly assign the value to the userID attribute.

Composing Relationships with the Nil Key Path

It is also worth noting that the nil source key path can also be useful when mapping relationships. There are times when a JSON document may contain non-nested values that you wish to compose a relationship from. Consider the following JSON:

    { "first_name": "Example", "last_name": "McUser", "city": "New York City", "state": "New York", "zip": 10038 }

and the corresponding models:

@interface RKExampleAddress : NSObject
@property (nonatomic, copy) NSString *city;
@property (nonatomic, copy) NSString *state;
@property (nonatomic, copy) NSNumber *zipCode;
@end

@interface RKExampleUser : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic) RKExampleAddress *address;
@end

Notice that we have a flat JSON structure, but wish to construct two models from this JSON. Let's take a look at the mapping configuration:

RKObjectMapping *addressMapping = [RKObjectMapping mappingForClass:[RKExampleAddress class]];
[addressMapping addAttributeMappingsFromDictionary:@{ @"city": @"city", @"state": @"state", @"zip": @"zipCode" }];

RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKExampleUser class]];
[userMapping addAttributeMappingsFromDictionary:@{ @"first_name": @"firstName", @"last_name": @"lastName" }];
[userMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:nil toKeyPath:@"address" withMapping:addressMapping]];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping method:RKRequestMethodAny pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];

Notice the use of the nil source key path in the construction of the RKRelationshipMapping. Because the source key is nil, all of the key-paths within the addressMapping are evaluated against the original JSON representation, but the mapped RKExampleAddress object is assigned to the address property on the parent RKExampleAddress.

Core Data

Until now we have focused on transient objects within RestKit. For many applications transient objects are completely the right choice -- if your data set is constantly changing and your use-cases can rely on the availability of network access, using transient objects is a simpler, easier way forward. But for some applications, you really need the full power of a queryable, persistent object model for performance, flexibility, offline access, etc. Apple has provided a great solution in Core Data. RestKit integrates with Core Data to bridge the gap between your remote server backend and your local object model. Since Core Data managed objects are KVC compliant, we get much of the integration "for free". But there are some Core Data specific steps and features that you must understand to leverage the persistence.

First off, when you begin using Core Data you must import the Core Data headers, then configure an object store and connect it to your object manager. The object store is a RestKit component that handles the details of setting of a Core Data environment that is backed with a SQLite database. Let's take a look at how this works:

#import <RestKit/RestKit.h>
#import <RestKit/CoreData.h>

RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MyApplication" ofType:@"momd"]];
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;

Now that we have set up the object store, we can configure persistent mappings. Let's say that we want to take our familiar Article object and make it persistent. You'll have to create a Core Data Managed Object Model and add it to your project. The configuration is outside the scope of this document, but there are great resources about how this works all over the net. Having done that, we'll update the Article interface & implementation, then configure a managed object mapping:

@interface Article : NSManagedObject
    @property (nonatomic, retain) NSNumber* articleID;
    @property (nonatomic, retain) NSString* title;
    @property (nonatomic, retain) NSString* body;
    @property (nonatomic, retain) NSDate*   publicationDate;
@end

@implementation Article
// We use @dynamic for the properties in Core Data
@dynamic articleID;
@dynamic title;
@dynamic body;
@dynamic publicationDate;
@end

// Now for the object mappings
RKEntityMapping* articleMapping = [RKEntityMapping mappingForEntityForName:@"Article" inManagedObjectStore:managedObjectStore];
[articleMapping addAttributeMappingsFromDictionary:@{
    @"id": @"articleID",
    @"title": @"title",
    @"body": @"body",
    @"publication_date": @"publicationDate"
}];
articleMapping.identificationAttributes = @[ @"articleID" ];

The astute reader will notice a couple of things:

  1. We changed our inheritance to NSManagedObject from NSObject
  2. Our properties are implemented via @dynamic
  3. We have added a new property -- articleID. Typically when you load a remote object it is going to include a unique primary key attribute that uniquely identifies that particular entity. This attribute is typically either an integer or a string (e.g., a UUID or permalink).
  4. We instantiated RKEntityMapping instead of RKObjectMapping.
  5. We have added a new key-path to attribute mapping specifying that we expect an "id" attribute in the payload. In our JSON, we'd see a fragment like "id": 12345 added to the dictionary for each Article.
  6. We have a new property set on the articleMapping: identificationAttributes. This property is significant because it helps RestKit understand how to uniquely identify your objects and perform intelligent updates of existing instances. The identificationAttributes is used to look up an existing object instance by one or more attributes and map updates onto that object instance. If you do not specify a value for identificationAttributes, then you will get new objects created every time you trigger object mapping.

Connecting Non-Nested Relationships Using Foreign Keys

JSON attributes are sometimes foreign key identifiers to existing Core Data entities, such as entities obtained by a separate RKObjectRequestOperation. RestKit provides a method to map to an entity using a foreign key identifier. Consider the following JSON:

{ "cities": [
      { "cityID": 1, "cityName": "Chicago" },
      { "cityID": 2, "cityName": "New York" }],
  "users": [
      { "userID":101, "userName": "Jeff", "cityID": 1 },
      { "userID":102, "userName": "Mary", "cityID": 2 },
      { "userID":103, "userName": "Sam",  "cityID": 1 }]
}
 

and the corresponding models where User has a Core Data relationship to City:

@interface City: NSManagedObject
    @property (nonatomic, retain) NSNumber* cityID;
    @property (nonatomic, retain) NSString* cityName;
@end

@interface User: NSManagedObject
    @property (nonatomic, retain) NSNumber* userID;
    @property (nonatomic, retain) NSString* userName;
    @property (nonatomic, retain) NSNumber* cityID;
    @property (nonatomic, retain) City* city;
@end

To connect the User entity to the City entity referenced by cityID property, you do the following after creating the RKEntityMapping for the User:

[userMapping addConnectionForRelationship:@"city" connectedBy:@{@"cityID": @"cityID"}];

The relationship value @"city" specifies the relationship object or name of the relationship object that is to be connected. The connectedBy: parameter is an NSDictionary mapping the User attribute to the City attribute. Since the attribute names are identical, the connectedBy: parameter can be also specified by an NSString, i.e. @"cityID".

Handling Multiple Root Objects in Core Data Post/Put

Sometimes when performing a PUT or POST with a NSManagedObject using RKObjectManager, you want to receive more than just the original object in response from the server. For example, your JSON may look like the following if you were using primary keys to build relationships:

{
    "article": { "title": "RestKit Object Mapping Intro",
                "body": "This article details how to use RestKit object mapping...",
                "authorIds": [13, 15],
                "publication_date": "7/4/2011"
            },
    "authors": [
        {
            "name": "Blake Watters",
            "email": "blake@gmail.com",
            "id": 13
        },
        {
            "name": "Bob Spryn",
            "email": "bob@gmail.com",
            "id": 15
        }
    ]
}

By default when postObject or putObject are used, RestKit is automatically going to try to map the JSON result into the sourceObject that was posted.

In our scenario what you need to do is nil out the targetObject in a block, and then the standard key-value coding mapping will take over (assuming your root objects are keyed correctly in the json):

RKManagedObjectRequestOperation *operation = [objectManager appropriateObjectRequestOperationWithObject:article method:RKRequestMethodPOST path:@"/whatever" parameters:nil];
operation.targetObject = nil;
operation.targetObjectID = nil;
[objectManager enqueueObjectRequestOperation:operation];

Handling Dynamic Nesting Attributes

A common, though somewhat annoying pattern in some JSON API's is the use of dynamic attributes as the keys for mappable object data. This commonly shows up with JSON like the following:

{ "blake": {
    "email": "blake@restkit.org",
    "favorite_animal": "Monkey"
    }
}

We might have a User class like the following:

@interface User : NSObject
@property (nonatomic, copy) NSString* email
@property (nonatomic, copy) NSString* username;
@property (nonatomic, copy) NSString* favoriteAnimal;
@end

You will note that this JSON is problematic compared to our earlier examples because the username attribute's data exists as the key in a dictionary, rather than a value. We handle this by creating an object mapping and using a new type of mapping definition:

RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class] ];
[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"username"];
[mapping addAttributeMappingsFromDictionary:@{
    @"(username).email": @"email",
    @"(username).favorite_animal": @"favoriteAnimal"
}];

What happens with this type of object mapping is that when applied against a dictionary of data, the keys are interpreted to contain the value for the nesting attribute (so "blake" becomes username). When the remaining attribute and relationship key paths are evaluated against the parsed data, the value of the nesting attribute is substituted into the key path before it is applied. So your @"(username).email" key path becomes @"blake.email" and the mapping continues.

Note that there are annoying limitations with this. It is common for many API's to use e-mail addresses as dynamic keys in this fashion. This doesn't fly with KVC because the @ character is used to denote array operations.

There is also a subtlety with nesting mappings and collections like this:

{
  "blake": {        
    "email": "blake@restkit.org",        
    "favorite_animal": "Monkey"    
  },    
  "sarah": {
    "email": "sarah@restkit.org",   
    "favorite_animal": "Cat"
  }
}

In these cases it is impossible for RestKit to automatically determine if the dictionary represents a single object or a collection with dynamic attributes. In these cases, you must give RestKit a hint if you have a collection:

RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class] ];
mapping.forceCollectionMapping = YES;
[mapping addAttributeMappingFromKeyOfRepresentationToAttribute:@"username"];
[mapping addAttributeMappingsFromDictionary:@{
    @"(username).email": @"email",
    @"(username).favorite_animal": "favoriteAnimal"
}];

Dynamic Object Mapping

Thus far we have examined clear-cut cases where the appropriate object mapping can be determined either by consulting the key path or by the developer directly providing the mapping. Sometimes it is desirable to dynamically determine the appropriate object mapping to use at mapping time. Perhaps we have a collection of objects with identical attribute names, but we wish to represent them differently. Or maybe we are loading a collection of objects that are not KVC compliant, but contain a mixture of types that we would like to model. RestKit supports such use cases via the RKDynamicMapping class. RKDynamicMapping is a sibling class to RKObjectMapping and can be added to RKRequestDescriptor and RKResponseDescriptor objects and used to configure RKMappingOperation instances. RKDynamicMapping allows you to hook into the mapping process and determine an appropriate concrete RKObjectMapping to use on a per-object basis.

When RestKit is performing a mapping operation and the current mapping being applied is an RKDynamicMapping instance, the dynamic mapping will be sent the objectMappingForRepresentation: message with the object representation that is currently being mapped. The dynamic mapping is responsible for introspecting the contents of the object and returning an RKObjectMapping instance that can be used to map the data into a concrete object.

There are two ways in which the determination of the appropriate object mapping can be made:

  1. Via a declarative matcher on an attribute within the mappable data. If your dynamic data contains an attribute that can be used to infer the appropriate object type, then you are in luck -- RestKit can handle the dynamic mapping via simple configuration.
  2. Via a block callback. If your data requires some special analysis or you want to dynamically construct an object mapping to handle the data, you can assign a delegate to the RKDynamicMapping and you will be called back to perform whatever logic you need to implement the object mapping lookup/construction.

To illustrate these concepts, let's consider the following JSON fragment:

{
    "people": [
        {
            "name": "Blake Watters",
            "type": "Boy",
            "friends": [
                {
                    "name": "John Doe",
                    "type": "Boy"
                },
                {
                    "name": "Jane Doe",
                    "type": "Girl"
                }
            ]
        },
        {
            "name": "Sarah",
            "type": "Girl"
        }
    ]
}

In this JSON we have a dictionary containing an array of people at the "people" key path. We want to map each of the people within that collection into different classes: Boy and Girl. Our meaningful attributes are the name and the friends, which is itself a dynamic collection of people. The type attribute will be used to determine what the appropriate destination mapping and class will be. Let's set it up:

// Basic setup
RKObjectMapping* boyMapping = [RKObjectMapping mappingForClass:[Boy class] ];
[boyMapping addAttributeMappingsFromArray:@[ @"name" ]];
RKObjectMapping* girlMapping = [RKObjectMapping mappingForClass:[Girl class] ];
[girlMapping addAttributeMappingsFromArray:@[ @"name" ]];
RKDynamicMapping* dynamicMapping = [RKDynamicMapping new];
[boyMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:dynamicMapping]];
[girlMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:@"friends" toKeyPath:@"friends" withMapping:dynamicMapping]];

// Connect a response descriptor for our dynamic mapping
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:dynamicMapping method:RKRequestMethodAny pathPattern:nil keyPath:@"people" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];

// Option 1: Configure the dynamic mapping via matchers
[dynamicMapping setObjectMapping:boyMapping whenValueOfKeyPath:@"type" isEqualTo:@"Boy"];
[dynamicMapping setObjectMapping:girlMapping whenValueOfKeyPath:@"type" isEqualTo:@"Girl"];

// Option 2: Configure the dynamic mapping via a block
[dynamicMapping setObjectMappingForRepresentationBlock:^RKObjectMapping *(id representation) {
    if ([[representation valueForKey:@"type"] isEqualToString:@"Boy"]) {
        return boyMapping;
    } else if ([[representation valueForKey:@"type"] isEqualToString:@"Girl"]) {
        return girlMapping;
    }
    
    return nil;
};

Notable within this code are the calls to setObjectMapping:whenValueOfKeyPath:isEqualTo:. This is the declarative matcher form of dynamic configuration. When you use these matchers, RestKit will invoke valueForKeyPath: on your mappable data and then attempt to compare the resulting value with the value provided in the invocation. If you have a simple string or numeric value that can be used to differentiate your mappings, then you don't need to use the delegate or block callbacks at all to perform dynamic mapping.

That's all there is to it. RestKit will invoke the dynamic mapping with the data and apply whatever object mapping is returned to that data. You can even decline the mapping of individual elements by returning a nil mapping. This can be useful to filter out unwanted information deep within an object graph.

Key-value Validation

RestKit supports the use of key-value validation at mapping time. This permits a number of helpful additions to your workflow. Using KVC validation, you can:

  1. Reject inappropriate values coming back from the server (note that for Core Data the entire object must be rejected from being saved and support must be enabled using the discardsInvalidObjectsOnInsert property)
  2. Perform custom transformations of values returned from the server.
  3. Fail out the mapping operation using custom logic.

Unlike the vast majority of the work we have done thus far, key-value validation is performed by adding methods onto your model class. KVC validation is a standard part of the Cocoa stack, but must be manually invoked on NSObject's. It is always performed for you on Core Data managed object when the managed object context is saved. RestKit provides KVC validation for you when object mapping is taking place.

Let's take a look at how you can leverage key-value validation to perform the above three tasks on our familiar Article object:

@implementation Article
- (BOOL)validateTitle:(id *)ioValue error:(NSError **)outError {
    // Force the title to uppercase
    *ioValue = [(NSString*)*ioValue uppercaseString];
    return YES;
}

- (BOOL)validateArticleID:(id *)ioValue error:(NSError **)outError {
    // Reject an article ID of zero. By returning NO, we refused the assignment and the value will not be set
    if ([(NSNumber*)*ioValue intValue] == 0) {
        return NO;
    }
    
    return YES;
}
 
- (BOOL)validateBody:(id *)ioValue error:(NSError **)outError {
    // If the body is blank, return NO and fail out the operation.
    if ([(NSString*)*ioValue length] == 0) {
        *outError = [NSError errorWithDomain:RKErrorDomain code:RKObjectMapperErrorUnmappableContent userInfo:nil];
        return NO;
    }
    
    return YES;
}
@end

These three methods will get invoked when the appropriate attribute is going to be set on the Article objects being mapped. The ioValue is a pointer to a pointer of the object reference that will be assigned to attribute. This means that we can completely change the value being assigned to anything that we want. If we return NO from the function, the assignment will not take place. We can also return NO and construct an error object and set the outError. This will cause mapping to fail and the error will bubble back up the RestKit stack.

Edit: while this is true, a side effect of the way RestKit maps values means that the property validation methods will only be called when the source and destination objects are of the same class. Practically speaking, this means you can't use this mechanism to convert an NSString * into a UIColor *, for example.

Look at the NSKeyValueCoding.h and search the web for more info about key-value validation in general.

Class Hierarchy

  • RKObjectManager - The external client interface for performing object mapping operations on resources loaded from the network. The object manager is responsible for creating object request operations and brokering interactions between the application and object mapping subsystem.
  • RKObjectRequestOperation - A subclass of NSOperation that sends an HTTP request and performs an object mapping operation on the response body. Responsible for parsing the payload appropriately and initializing an RKResponseMapperOperation to handle the mapping.
  • RKObjectMapping - A definition of an object mapping for a particular class. Contains a collection of attribute mappings defining how attributes in a particular mappable object should be mapped onto the target class. Also contains relationship mappings specifying how to map nested object structures into one-to-one or one-to-many relationships. Object mappings are registered with the mapping provider to define rules for mapping and serializing objects.
  • RKAttributeMapping - Defines a mapping from a source keyPath to a destination keyPath within an object mapping definition. For example, defines a rule that the NSString attribute at the created_at keyPath maps to the NSString property at the createdAt keyPath on the destination object.
  • RKRelationshipMapping - A subclass of RKPropertyMapping that defines a mapping to a related mappable object. Includes an objectMapping property defining the rules for mapping the related object. Used for transforming nested arrays and dictionaries into related objects.
  • RKMapperOperation - The interface for performing object mapping on mappable data. The mapper takes an a KVC compliant object representation or array of representations and a dictionary key path to object mappings and constructs an RKMappingResult by executing instances of RKMappingOperation and evaluating the results.
  • RKMappingOperation - Responsible for applying an object mapping to a particular mappable representation. Evaluates the attribute mappings contained in the RKObjectMapping against the mappable dictionary and assigns the results to the target object. Recursively creates child mapping operations for all relationships and continues on until a full object graph has been constructed according to the mapping rules.
  • RKMappingResult - When RKMapperOperation has finished its work, it will either return nil to indicate an unrecoverable error occurred or will return an instance of RKMappingResult. The mapping result enables you to coerce the mapped results into the format you wish to work with. The currently available result coercions are:
    1. firstObject - Return the result as a single object. Useful when you know you have mapped a single object back (e.g., used postObject).
    2. array - Return the result as an array of mapped objects. This will take all the mapped keyPaths and combine all mapped objects under those keyPaths and return it as a single array of objects.
    3. dictionary - Return the result as a dictionary of keyPaths and mapped object pairs. Useful when you want to identify your results by keyPath.
  • RKRouter - Responsible for generating NSURL objects for accessing remote representations of objects. Capable of generating a URL by interpolating property values into a string. For example, a path of "/articles/:articleID" when applied to an Article object with a articleID property with the value 12345, would generate "/articles/12345". The object router is used to generate resource paths when getObject, postObject, putObject and deleteObject are invoked.
  • RKErrorMessage - A simple class providing for the mapping of server-side error messages back to NSError objects. Contains a single errorMessage property. When an RKObjectManager is initialized, response descriptors with the "error" and "errors" keyPaths and mappings to RKErrorMessage are registered. This provides out of the box mapping support for simple error messages. The mappings can be removed or replaced by the developer to handle more advanced error return values, such as containing a server side error code or other metadata.
  • RKPropertyInspector - An internally used singleton object responsible for cacheing the properties and types of a target class. This is used during object mapping to transform values at mapping time based on the source and destination types.
  • RKObjectParameterization - Responsible for taking Cocoa objects are mapping them into a dictionary of parameters for transport via HTTP to a remote server. The parameterization takes an instance of an object and maps it back into an NSMutableDictionary representation by creating an RKMappingOperation. During the mapping process, certain types are transformed into serializable representation (e.g., NSDate & NSDecimalNumber objects are coerced into NSString instances).
  • RKMIMETypeSerialization - Responsible for maintaining the association between MIME Types and the RKSerialization class implementations responsible for handling it. A serialization for the 'application/json' MIME Type is registered automatically for the RKNSJSONSerialization class.

Tasks

Configuring an Object Mapping

// In this use-case Article is a vanilla NSObject with properties
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[Article class] ];

// Add an attribute mapping to the object mapping directly
RKObjectAttributeMapping* titleMapping = [RKAttributeMapping mappingFromKeyPath:@"title" toKeyPath:@"title"];
[mapping addPropertyMapping:titleMapping];

// Configure attribute mappings using helper methods
[mapping addAttributeMappingsFromArray:@[ @"title", @"body" ]]; // Define mappings where the keyPath and target attribute have the same name
[mapping addAttributeMappingsFromDictionary:@{
    @"some.keyPath": @"targetAttribute1",
    @"another.keyPath": @"targetAttribute2"
}];

// Configure relationship mappings
RKObjectMapping* commentMapping = [RKObjectMapping mappingForClass:[Comment class] ];
// Direct configuration of instances
RKObjectRelationshipMapping* articleCommentsMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:@"comments" toKeyPath:@"comments" withMapping:commentMapping];
[mapping addPropertyMapping:articleCommentsMapping];

// Configuration using helper methods
[mapping addRelationshipMappingWithSourceKeyPath:@"comments" mapping:commentMapping];

Configuring a Core Data Object Mapping

#import <RestKit/RestKit.h>
#import <RestKit/CoreData.h>

RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error;
[managedObjectStore addSQLitePersistentStoreAtPath:[RKApplicationDataDirectory() stringByAppendingPathComponent:@"MyApp.sqlite"] fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
objectManager.managedObjectStore = managedObjectStore;

RKManagedObjectMapping* articleMapping = [RKManagedObjectMapping mappingForClass:[Article class] ];
[articleMapping addAttributeMappingsFromDictionary:@{
    @"id": @"articleID",
    @"title": @"title",
    @"body": @"body",
    @"publication_date": @"publicationDate"
}];
articleMapping.identificationAttributes = @[ @"articleID" ];

Configuring the Serialization Format

// Serialize to Form Encoded
[RKObjectManager sharedManager].requestSerializationMIMEType = RKMIMETypeFormURLEncoded;

// Serialize to JSON
[RKObjectManager sharedManager].requestSerializationMIMEType = RKMIMETypeJSON;

Object Serialization Tasks

This is handled for you when using postObject and putObject, presented here for reference

User* user = [User new];
user.firstName = @"Blake";
user.lastName = @"Watters";

RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[NSDictionary class] ];
[mapping addAttributeMappingsFromArray:@[ @"firstName", @"lastName"] ];

RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[User class] rootKeyPath:@"user" method:RKRequestMethodAny];
NSError* error;
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];

// Serialize the object to JSON
NSData *JSON = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];

Performing a Mapping

This is handled for you when using loadObjectAtResourcePath:, presented here for reference:

NSString* JSONString = @"{ \\"name\\": \\"The name\\", \\"number\\": 12345}";
NSString* MIMEType = @"application/json";
NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) {
    // Parser error...
}

NSDictionary *mappingsDictionary = @{ @"someKeyPath": someMapping };
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
    // Yay! Mapping finished successfully
}

Registering a Serialization Class

[RKMIMETypeSerialization registerClass:[MySerialization class] forMIMEType:@"this/that"];