mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-01-12 22:51:50 +08:00
964 lines
52 KiB
Markdown
964 lines
52 KiB
Markdown
# Object Mapping
|
|
|
|
This document details the design of object mapping in RestKit as of version 0.9.3
|
|
|
|
## Object Mapping Overview
|
|
|
|
RestKit's key differentiating feature from other HTTP toolkits in the iOS space is the
|
|
tightly integrated support for object mapping. Object mapping is the process of taking a representation
|
|
of data in one format and transforming it into another form. This mechanism is used extensively within
|
|
RestKit to streamline the serialization and deserialization of resources exchanged with a remote
|
|
backend application server via HTTP. Object mapping operations are performed when you load a remote
|
|
resource via an instance of `RKObjectManager` and when a local object is sent to the backend for processing.
|
|
|
|
### 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.
|
|
|
|
## 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:
|
|
|
|
```json
|
|
{ "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:
|
|
|
|
```objc
|
|
@interface Article : NSObject
|
|
@property (nonatomic, retain) NSString* title;
|
|
@property (nonatomic, retain) NSString* body;
|
|
@property (nonatomic, retain) NSString* author;
|
|
@property (nonatomic, retain) 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 a parsed data payload into a local domain object.
|
|
Each object mapping is composed of a collection of attribute and relationship mappings that define how to transform key paths in the
|
|
source data into properties on the local object.
|
|
1. **RKObjectMappingProvider**: The object mapping provider defines the rules for what key paths in a loaded set of data correspond to
|
|
an object mapping.
|
|
|
|
Let's take a look at how we would configure RestKit to perform this operation:
|
|
|
|
```objc
|
|
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"author" toAttribute:@"author"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
|
|
[[RKObjectManager sharedManager].mappingProvider setMapping:articleMapping forKeyPath:@"articles"];
|
|
```
|
|
|
|
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 instructed the mapping provider to use `articleMapping` whenever it encounters data at the `@"articles"`
|
|
key path.
|
|
|
|
Now that we have configured our object mapping, we can load this collection:
|
|
|
|
```objc
|
|
- (void)loadArticles {
|
|
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:@"/articles" delegate:self];
|
|
}
|
|
|
|
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
|
|
RKLogInfo(@"Load collection of Articles: %@", objects);
|
|
}
|
|
```
|
|
|
|
Recall the importance of key-value coding to the process. When we loaded the example JSON above via RestKit with the articleMapping configuration in
|
|
place, the following things happened:
|
|
|
|
1. RestKit created an instance of `RKObjectMapper` with the parsed JSON payload and the mapping provider. The mapper is responsible for figuring
|
|
out how to map an opaque collection of potentially mappable data.
|
|
1. The `RKObjectMapper` instance asked the mapping provider for a list of mappable key paths. Each key path was evaluated against the parsed
|
|
payload using valueForKeyPath:. Since we configured a mapping for the `@"articles"` key path, RestKit invoked `valueForKeyPath:@"articles"` on the
|
|
parsed data and found the mappable data.
|
|
1. RestKit now knows that there is interesting data in the payload that needs to be mapped. `RKObjectMapper` notes that the mappable data is an array
|
|
and iterates over the collection, mapping each dictionary within the array in turn. An instance of RKObjectMappingOperation is created for each element
|
|
in the array. Each mapping operation targets one of dictionaries contained in the array returned from the `@"articles"` key path. For the example above, this
|
|
means that we would generate two object mapping operations:
|
|
|
|
```json
|
|
// This dictionary will 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 defined via the calls to
|
|
`mapKeyPath:toAttribute:` 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
|
|
(i.e. 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 (i.e.
|
|
we mapped `publication_date` to `publicationDate` so that it fits better with Cocoa naming conventions).
|
|
|
|
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/XML
|
|
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.
|
|
|
|
### Parameters
|
|
|
|
GET query parameters should be added with the new NSString method appendQueryParameters. For example,
|
|
|
|
```objc
|
|
- (void)loadArticlesContainingText:(NSString *)searchText {
|
|
NSDictionary *queryParams = [NSDictionary dictionaryWithObject:searchText forKey:@"q"];
|
|
NSString *resourcePath = [@"/articles" appendQueryParameters:queryParams];
|
|
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath delegate:self];
|
|
}
|
|
```
|
|
|
|
POST parameters can be provided by setting the RKObjectLoader's params property:
|
|
|
|
```objc
|
|
- (void)saveArticleWithAdditionalParams:(NSDictionary *)extraPOSTParams {
|
|
RKObjectLoader* loader = [objectManager sendObject:human delegate:responseLoader block:^(RKObjectLoader* loader) {
|
|
loader.method = RKRequestMethodPOST;
|
|
loader.resourcePath = @"/articles/create";
|
|
loader.params = extraPOSTParams;
|
|
}];
|
|
}
|
|
```
|
|
|
|
## 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.
|
|
|
|
### Type 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. The following table enumerates a number of available transformations
|
|
from a source type to a destination type that are automatically applied when you define an attribute mapping:
|
|
|
|
<table>
|
|
<tr style="font-weight: bold;">
|
|
<th>Source Type</th>
|
|
<th>Destination Type</th>
|
|
<th>Discussion</th>
|
|
</th>
|
|
<tr>
|
|
<td>NSString</td>
|
|
<td>NSDate</td>
|
|
<td>NSString values are mapped to NSDate properties by applying the date format strings from the RKObjectMapping instance.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSString</td>
|
|
<td>NSURL</td>
|
|
<td>NSString values are mapped to NSURL properties via <pre><code>[NSURL URLWithString:(NSString*)value]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSString</td>
|
|
<td>NSDecimalNumber</td>
|
|
<td>NSString values are mapped to NSDecimalNumber properties via <pre><code>[NSDecimalNumber decimalNumberWithString:(NSString*)value]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSString</td>
|
|
<td>NSNumber</td>
|
|
<td>NSString values are mapped to NSNumber properties via <pre><code>[NSNumber numberWithDouble:[(NSString*)value doubleValue]]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSString containing YES, NO, true, false, t, f</td>
|
|
<td>NSNumber</td>
|
|
<td>NSString values containing a known boolean string are mapped to NSNumber properties via <pre><code>[NSNumber numberWithBool:boolValueFromString]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSSet</td>
|
|
<td>NSArray</td>
|
|
<td>NSSet values are mapped to NSArray properties via <pre><code>[(NSSet*)value allObjects]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSArray</td>
|
|
<td>NSSet</td>
|
|
<td>NSArray values are mapped to NSSet properties via <pre><code>[NSSet setWithArray:value]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSNumber</td>
|
|
<td>NSDate</td>
|
|
<td>NSNumber values are mapped to NSDate properties via <pre><code>[NSDate dateWithTimeIntervalSince1970:[(NSNumber*)value intValue]]</pre></code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSCFBoolean</td>
|
|
<td>NSString</td>
|
|
<td>Boolean literals true and false parsed from JSON are mapped to NSString properties as @"true" and @"false"</td>
|
|
</tr>
|
|
<tr>
|
|
<td>NSNull</td>
|
|
<td>Anything</td>
|
|
<td>NSNull entries (null in JSON) are mapped to nil for any destination property.</td>
|
|
</tr>
|
|
<tr>
|
|
<td>respondsToSelector:@selector(stringValue)</td>
|
|
<td>NSString</td>
|
|
<td>Any mappable value can be mapped to an NSString property if the object responds to the stringValue selector. This works for NSNumbers, etc.</td>
|
|
</tr>
|
|
</table>
|
|
|
|
### 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:
|
|
|
|
```json
|
|
{ "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:
|
|
|
|
```objc
|
|
@interface Author : NSObject
|
|
@property (nonatomic, retain) NSString* name;
|
|
@property (nonatomic, retain) NSString* email;
|
|
@end
|
|
```
|
|
|
|
We also need to change `author` property type in `Article` from `NSString*` to `Author*`:
|
|
|
|
```objc
|
|
@interface Article : NSObject
|
|
@property (nonatomic, retain) NSString* title;
|
|
@property (nonatomic, retain) NSString* body;
|
|
@property (nonatomic, retain) Author* author;
|
|
@property (nonatomic, retain) 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:
|
|
|
|
```objc
|
|
// Create our new Author mapping
|
|
RKObjectMapping* authorMapping = [RKObjectMapping mappingForClass:[Author class]];
|
|
// NOTE: When your source and destination key paths are symmetrical, you can use mapAttributes: as a shortcut
|
|
[authorMapping mapAttributes:@"name", @"email", nil];
|
|
|
|
// Now configure the Article mapping
|
|
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
|
|
// Define the relationship mapping
|
|
[articleMapping mapKeyPath:@"author" toRelationship:@"author" withMapping:authorMapping];
|
|
|
|
[[RKObjectManager sharedManager].mappingProvider setMapping:articleMapping forKeyPath:@"articles"];
|
|
```
|
|
|
|
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 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 serializing local objects into a textual format for submission back to your backend system for processing. This facility
|
|
is provided by the `RKObjectSerializer` 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 serialization. In addition to its duties in providing the object mapper with mappings for key paths,
|
|
`RKObjectMappingProvider` has a secondary responsibility of providing the appropriate mapping for serializing an object of a given type. There are a couple of
|
|
options for configuring serialization that are best understood through code:
|
|
|
|
```objc
|
|
// Configure a serialization mapping for our Article class. We want to send back title, body, and publicationDate
|
|
RKObjectMapping* articleSerializationMapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]];
|
|
[articleSerializationMapping mapAttributes:@"name", @"body", @"publicationDate", nil];
|
|
|
|
// Now register the mapping with the provider
|
|
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:articleSerializationMapping forClass:[Article class]];
|
|
```
|
|
|
|
We have now built an object mapping that can take our local `Article` objects and turn them back into `NSMutableDictionary` instances and
|
|
we have told the mapping provider how the mapping and classes are related. Life is good -- let's see how it comes into play in the app:
|
|
|
|
```objc
|
|
// 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 delegate:self];
|
|
```
|
|
|
|
So how do these pieces connect, you may be asking? When we use postObject:, we are essentially asking RestKit to create and perform an
|
|
object serialization on our behalf. Behind the scenes, the following things have just happened:
|
|
|
|
1. `RKObjectManager` initializes an `RKObjectLoader` instance with a sourceObject property targeting our article object.
|
|
1. The `serializationMIMEType` property of the `RKObjectLoader` is set to value of the `serializationMIMEType` property on `RKObjectManager`. This
|
|
establishes the destination format to serialize the object into (either URL Form Encoded or JSON, as of this writing). More on this in a moment.
|
|
1. `RKObjectManager` asks the `mappingProvider` for the `serializationMappingForClass:[Article class]` to obtain the serialization mapping
|
|
for the source object. The serialization mapping is assigned to the `RKObjectLoader` instance on the `serializationMapping` property.
|
|
1. The `RKObjectLoader` notices that it has a sourceObject and is performing a POST or PUT request. This triggers serialization to kick in.
|
|
1. The `RKObjectLoader` instance initializes an `RKObjectSerializer` with the `sourceObject` and `serializationMapping` configured on the loader.
|
|
1. The `RKObjectSerializer` is invoked to build and return a serialized representation of the object in the `serializationMIMEType` format and that
|
|
value is assigned to the body of the loader.
|
|
1. The asynchronous request is sent off for processing with the serialized data in tow.
|
|
|
|
We have packed quite a bit of power into just a few lines of code. Let's fill in some of the missing details. As mentioned above, the format the data
|
|
takes when being assigned to the request is determined by the value of the `serializationMIMEType` property. We can change this easily:
|
|
|
|
```objc
|
|
// Globally use JSON as the wire format for POST/PUT operations
|
|
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeJSON;
|
|
|
|
// Or switch on a per request basis
|
|
RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] objectLoaderForObject:article method:RKRequestMethodPOST delegate:self];
|
|
objectLoader.serializationMIMEType = RKMIMETypeFormURLEncoded;
|
|
[objectLoader send];
|
|
```
|
|
|
|
Because serialization uses `NSMutableDictionary` as the intermediate format, new MIME Types such as XML, Protocol Buffers, BSON, etc. can be added
|
|
down the line without major changes to the serializer. We'll examine how this all works in greater depth in the `RKParser` discussion.
|
|
|
|
Something else you may have noticed when configuring the serialization mapping is that most of the time our serialization mappings are extremely similar
|
|
to our object 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:
|
|
|
|
```objc
|
|
// Our familiar articlesMapping from earlier
|
|
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"author" toAttribute:@"author"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
|
|
// Build a serialization mapping by inverting our object mapping. Includes attributes and relationships
|
|
RKObjectMapping* articleSerializationMapping = [articleMapping inverseMapping];
|
|
// You can customize the mapping here as necessary -- adding/removing mappings
|
|
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:articleSerializationMapping forClass:[Article class]];
|
|
```
|
|
|
|
### 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 be explicit about how your
|
|
content is to be handled. Let's consider another example: Imagine that our weblog services returning articles works just as before,
|
|
but the JSON output looks like this:
|
|
|
|
```json
|
|
[
|
|
{ "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 a slight difference in the registration with the mapping provider:
|
|
|
|
```objc
|
|
// Our familiar articlesMapping from earlier
|
|
RKObjectMapping* articleMapping = [RKObjectMapping mappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"author" toAttribute:@"author"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
|
|
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:articleMapping];
|
|
```
|
|
|
|
Rather than leveraging `setMapping:forKeyPath:`, we have invoked `addObjectMapping:`. This method essentially adds a retained reference
|
|
to the mapping provider so that we can easily get the mapping back later when we need it via `objectMappingForClass:`. Let's take a look at how
|
|
we'd load this array of `Article` objects:
|
|
|
|
```objc
|
|
- (void)loadArticlesWithoutKVC {
|
|
RKObjectMapping* articleMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Article class]];
|
|
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:@"/articles" objectMapping:articleMapping delegate:self];
|
|
}
|
|
|
|
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
|
|
RKLogInfo(@"Load collection of Articles: %@", objects);
|
|
}
|
|
```
|
|
|
|
We've had to take the extra step of providing the object mapping directly to the object loader so that RestKit knows what to do with
|
|
the object. This can sometimes be necessary when using serialization as well. Consider another example: We have a system where we post an
|
|
ArticleQuery object and get back a collection of Articles. Our ArticleQuery object looks like this:
|
|
|
|
```objc
|
|
@interface ArticleQuery
|
|
@property (nonatomic, retain) NSString* searchTerm;
|
|
@property (nonatomic, retain) NSNumber* pageNumber;
|
|
@property (nonatomic, retain) NSNumber* yearPublished;
|
|
@end
|
|
```
|
|
|
|
We have configured a serialization mapping for the object and want to fetch our results:
|
|
|
|
```objc
|
|
- (void)loadArticlesUsingQuery {
|
|
ArticleQuery* query = [ArticleQuery new];
|
|
query.searchTerm = @"Monkey";
|
|
query.pageNumber = [NSNumber numberWithInt:5];
|
|
query.yearPublished = [NSNumber numberWithInt:2011];
|
|
|
|
RKObjectMapping* articleMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Article class]];
|
|
[[RKObjectManager sharedManager] postObject:query mapResponseWith:articleMapping delegate:self];
|
|
}
|
|
```
|
|
One inconvenience of working with non-KVC data is that RestKit is completely unable to automatically infer the appropriate mappings for you. If you
|
|
happen to be working with data where you post/put an object and receive the same object back, you can instruct RestKit to automatically select the
|
|
appropriate object mapping for you:
|
|
|
|
```objc
|
|
[RKObjectManager sharedManager].inferMappingsFromObjectTypes = YES;
|
|
[[RKObjectManager sharedManager] postObject:article delegate:self];
|
|
```
|
|
|
|
This will cause RestKit to search the mapping provider for the first registered mapping targeting the `Article` class and configure the
|
|
object loader to map the results with that mapping. This is provided as a convenience for users who cannot use KVC mappings and is disabled by
|
|
default.
|
|
|
|
### 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 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:
|
|
|
|
```objc
|
|
#import <RestKit/RestKit.h>
|
|
|
|
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:@"http://restkit.org"];
|
|
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:@"MyApp.sqlite"];
|
|
objectManager.objectStore = objectStore;
|
|
```
|
|
|
|
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:
|
|
|
|
```objc
|
|
@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 title;
|
|
@dynamic body;
|
|
@dynamic author;
|
|
@dynamic publicationDate;
|
|
@end
|
|
|
|
// Now for the object mappings
|
|
RKManagedObjectMapping* articleMapping = [RKManagedObjectMapping objectMappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"id" toAttribute:@"articleID"];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
articleMapping.primaryKeyAttribute = @"articleID";
|
|
```
|
|
|
|
The astute reader will notice a couple of things:
|
|
|
|
1. We changed our inheritance to NSManagedObject from NSObject
|
|
1. Our properties are now implemented via @dynamic instead of @synthesize
|
|
1. 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 (i.e. a UUID or permalink).
|
|
1. We instantiated `RKManagedObjectMapping` instead of `RKObjectMapping`.
|
|
1. 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.
|
|
1. We have a new property set on the articleMapping: `primaryKeyAttribute`. This property is significant because it helps RestKit
|
|
understand how to uniquely identify your objects and perform intelligently update existing instances. The primaryKeyAttribute is used
|
|
to look up an existing object instance by server-side primary key and map updates onto that object instance. If you do not specify a
|
|
primaryKeyAttribute, then you will get new objects created every time you trigger object mapping.
|
|
|
|
### 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:
|
|
|
|
```json
|
|
{ "blake": {
|
|
"email": "blake@restkit.org",
|
|
"favorite_animal": "Monkey"
|
|
}
|
|
}
|
|
```
|
|
|
|
We might have a User class like the following:
|
|
|
|
```objc
|
|
@interface User : NSObject
|
|
@property (nonatomic, retain) NSString* email
|
|
@property (nonatomic, retain) NSString* username;
|
|
@property (nonatomic, retain) 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:
|
|
|
|
```json
|
|
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class]];
|
|
[mapping mapKeyOfNestedDictionaryToAttribute:@"username"];
|
|
[mapping mapFromKeyPath:@"(username).email" toAttribute:"email"];
|
|
[mapping mapFromKeyPath:@"(username).favorite_animal" toAttribute:"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 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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[User class]];
|
|
mapping.forceCollectionMapping = YES;
|
|
[mapping mapKeyOfNestedDictionaryToAttribute:@"username"];
|
|
[mapping mapFromKeyPath:@"(username).email" toAttribute:"email"];
|
|
[mapping mapFromKeyPath:@"(username).favorite_animal" toAttribute:"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 RKDynamicObjectMapping class.
|
|
RKDynamicObjectMapping is a sibling class to RKObjectMapping and can be added to instances of RKObjectMappingProvider and
|
|
used to configure RKObjectMappingOperation instances. RKDynamicObjectMapping allows you to hook into the mapping process
|
|
and determine an appropriate RKObjectMapping to use on a per-object basis.
|
|
|
|
When RestKit is performing a mapping operation and the current mapping being applied is an RKDynamicObjectMapping instance,
|
|
the dynamic mapping will be sent the `objectMappingForDictionary:` message with the NSDictionary that is currently being
|
|
mapped. The dynamic mapping is responsible for introspecting the contents of the dictionary and returning an RKObjectMapping
|
|
instance that can be used to map the data into a concrete object.
|
|
|
|
There are three 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 delegate 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 RKDynamicObjectMapping and you will be called back to perform whatever
|
|
logic you need to implement the object mapping lookup/construction.
|
|
3. Via a delegate block invocation. Similar to the delegate configuration, you can assign a delegateBlock to the RKDynamicObjectMapping that will be invoked to determine the appropriate RKObjectMapping to use for the mappable data.
|
|
|
|
To illustrate these concepts, let's consider the following JSON fragment:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```objc
|
|
// Basic setup
|
|
RKObjectMapping* boyMapping = [RKObjectMapping mappingForClass:[Boy class]];
|
|
[boyMapping mapAttributes:@"name", nil];
|
|
RKObjectMapping* girlMapping = [RKObjectMapping mappingForClass:[Girl class]];
|
|
[girlMapping mapAttributes:@"name", nil];
|
|
RKDynamicObjectMapping* dynamicMapping = [RKDynamicObjectMapping dynamicMapping];
|
|
[boyMapping mapKeyPath:@"friends" toRelationship:@"friends" withMapping:dynamicMapping];
|
|
[girlMapping mapKeyPath:@"friends" toRelationship:@"friends" withMapping:dynamicMapping];
|
|
|
|
// Connect our mapping to RestKit's mapping provider
|
|
[[RKObjectManager sharedManager].mappingProvider setMapping:dynamicMapping forKeyPath:@"people"];
|
|
|
|
// Configure the dynamic mapping via matchers
|
|
[dynamicMapping setObjectMapping:boyMapping whenValueOfKeyPath:@"type" isEqualTo:@"Boy"];
|
|
[dynamicMapping setObjectMapping:girlMapping whenValueOfKeyPath:@"type" isEqualTo:@"Girl"];
|
|
|
|
// Configure the dynamic mapping via a delegate
|
|
dynamicMapping.delegate = self;
|
|
|
|
- (RKObjectMapping*)objectMappingForData:(id)data {
|
|
// Dynamically construct an object mapping for the data
|
|
if ([[data valueForKey:@"type"] isEqualToString:@"Girl"]) {
|
|
return [RKObjectMapping mappingForClass:[Girl class] block:^(RKObjectMapping* mapping) {
|
|
[mapping mapAttributes:@"name", nil];
|
|
}];
|
|
} else if ([[data valueForKey:@"type"] isEqualToString:@"Boy"]) {
|
|
return [RKObjectMapping mappingForClass:[Boy class] block:^(RKObjectMapping* mapping) {
|
|
[mapping mapAttributes:@"name", nil];
|
|
}];
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
// Configure the dynamic mapping via a block
|
|
dynamicMapping.objectMappingForDataBlock = ^ RKObjectMapping* (id mappableData) {
|
|
if ([[mappableData valueForKey:@"type"] isEqualToString:@"Boy"]) {
|
|
return boyMapping;
|
|
} else if ([[mappableData 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.
|
|
1. Perform custom transformations of values returned from the server.
|
|
1. 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:
|
|
|
|
```objc
|
|
@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:RKRestKitErrorDomain 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.
|
|
|
|
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 loaders and brokering interactions
|
|
between the application and object mapping subsystem.
|
|
- **RKObjectLoader** - A subclass of RKRequest that sends an HTTP request and performs an object mapping operation
|
|
on the resulting payload. Responsible for parsing the payload appropriately and initializing an `RKObjectMapper` to handle the mapping.
|
|
- **RKObjectMappingProvider** - Responsible for providing keyPaths and RKObjectMapping objects to instances of `RKObjectMapper`.
|
|
An instance of the mapping provider is available via the `mappingProvider` property of `RKObjectManager`. This mapping provider
|
|
is automatically assigned to all `RKObjectLoader` instances instantiated through the object manager.
|
|
- **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.
|
|
- **RKObjectAttributeMapping** - 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.
|
|
- **RKObjectRelationshipMapping** - A subclass of `RKObjectAttributeMapping` 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.
|
|
- **RKObjectMapper** - The interface for performing object mapping on mappable data. The mapper evaluates the type of the object
|
|
and obtains the appropriate object mapping from an `RKObjectMappingProvider` and applies it by creating instances of `RKObjectMappingOperation`.
|
|
- **RKObjectMappingOperation** - Responsible for applying an object mapping to a particular mappable dictionary. 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.
|
|
- **RKObjectMappingResult** - When `RKObjectMapper` has finished its work, it will either return nil to indicate an unrecoverable error occurred or
|
|
will return an instance of `RKObjectMappingResult`. 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. `asObject` - Return the result as a single object. Useful when you know you have mapped a single object back (i.e. used `postObject`).
|
|
1. `asCollection` - Return the result as a collection 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.
|
|
1. `asDictionary` - Return the result as a dictionary of keyPaths and mapped object pairs. Useful when you want to identify your results by keyPath.
|
|
1. `asError` - Return the result as an NSError. The error is constructed by coercing the result into a collection, then calling `description` on all
|
|
mapped objects to turn them into a string. The collection of error message strings are then joined together with the ", " delimiter to construct
|
|
the localizedDescription for the error. The raw error objects the error was mapped from is available on the userInfo of the NSError instance. This
|
|
is useful when you encountered a server side error and want to coerce the mapping results into an NSError. This is how `objectLoader:didFailWithError`
|
|
returns server side error messages to you.
|
|
- **RKObjectRouter** - Responsible for generating resource paths for accessing remote representations of objects. Capable of generating a resource
|
|
path 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, object mappings from the "error" and "errors" keyPaths to instances of RKErrorMessage are registered with the mapping provider. 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.
|
|
- **RKObjectPropertyInspector** - 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.
|
|
- **RKObjectSerializer** - Responsible for taking Cocoa objects are serializing them for transport via HTTP to a remote server. The serializer takes an instance of an object
|
|
and maps it back into an NSDictionary representation by creating an `RKObjectMappingOperation`. During the mapping process, certain types are transformed into serializable
|
|
representation (i.e. NSDate & NSDecimalNumber objects are coerced into NSString instances). Once mapping is complete, the NSDictionary instance is encoded into a wire format
|
|
and returned as an `RKRequestSerializable` object. The serializer is invoked for you automatically when `postObject` and `putObject` are invoked on the object manager. The
|
|
appropriate mapping is selected by consulting the `objectMappingForClass:` method of the `RKObjectMappingProvider` instance configured on the object manager. Currently serialization to form encoded and JSON is supported.
|
|
- **RKParserRegistry** - Responsible for maintaining the association between MIME Types and the `RKParser` object responsible for handling it. There is a singleton
|
|
sharedRegistry instance that is automatically configured at library initialization time to handle the application/json and application/xml MIME types. Runtime detection
|
|
of the parser classes is utilized to configure the registry appropriately. For JSON, autoconfiguration searches for JSONKit, then YAJL, and then SBJSON.
|
|
|
|
## Tasks
|
|
|
|
### Configuring an Object Mapping
|
|
```objc
|
|
// 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 = [RKObjectAttributeMapping mappingFromKeyPath:@"title" toKeyPath:@"title"];
|
|
[mapping addAttributeMapping:titleMapping];
|
|
|
|
// Configure attribute mappings using helper methods
|
|
[mapping mapAttributes:@"title", @"body", nil]; // Define mappings where the keyPath and target attribute have the same name
|
|
[mapping mapKeyPath:@"created_at" toAttribute:@"createdAt"]; // Map a differing keyPath and attribute
|
|
[mapping mapKeyPathsToAttributes:@"some.keyPath", @"targetAttribute1", @"another.keyPath", @"targetAttribute2", nil];
|
|
|
|
// Configure relationship mappings
|
|
RKObjectMapping* commentMapping = [[RKObjectManager sharedManager] objectMappingForClass:[Comment class]];
|
|
// Direct configuration of instances
|
|
RKObjectRelationshipMapping* articleCommentsMapping = [RKObjectRelationshipMapping mappingFromKeyPath:@"comments" toKeyPath:@"comments" withMapping:commentMapping];
|
|
[mapping addRelationshipMapping:articleCommentsMapping];
|
|
|
|
// Configuration using helper methods
|
|
[mapping mapRelationship:@"comments" withMapping:commentMapping];
|
|
[mapping hasMany:@"comments" withMapping:commentMapping];
|
|
[mapping belongsTo:@"user" withMapping:userMapping];
|
|
|
|
// Register the mapping with the object manager
|
|
[objectManager.mappingProvider setMapping:mapping forKeyPath:@"article"];
|
|
```
|
|
|
|
### Configuring a Core Data Object Mapping
|
|
```objc
|
|
#import <RestKit/RestKit.h>
|
|
|
|
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:@"http://restkit.org"];
|
|
RKManagedObjectStore* objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:@"MyApp.sqlite"];
|
|
objectManager.objectStore = objectStore;
|
|
|
|
RKManagedObjectMapping* articleMapping = [RKManagedObjectMapping objectMappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"id" toAttribute:@"articleID"];
|
|
[articleMapping mapKeyPath:@"title" toAttribute:@"title"];
|
|
[articleMapping mapKeyPath:@"body" toAttribute:@"body"];
|
|
[articleMapping mapKeyPath:@"publication_date" toAttribute:@"publicationDate"];
|
|
articleMapping.primaryKeyAttribute = @"articleID";
|
|
```
|
|
|
|
### Loading Using KeyPath Mapping Lookup
|
|
```objc
|
|
RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:@"http://restkit.org"];
|
|
RKManagedObjectMapping* articleMapping = [RKObjectMapping objectMappingForClass:[Article class]];
|
|
[articleMapping mapKeyPath:@"id" toAttribute:@"articleID"];
|
|
[objectManager.mappingProvider setMapping:articleMapping forKeyPath:@"articles"];
|
|
|
|
RKObjectLoader* loader = [RKObjectManager loadObjectsAtResourcePath:@"/articles" delegate:self];
|
|
|
|
/**
|
|
The object mapper will try to determine the mappings by examining keyPaths in the loaded payload. If
|
|
the payload contains a dictionary with data at the 'articles' key, it will be mapped
|
|
*/
|
|
```
|
|
|
|
### Load using an explicit mapping
|
|
```objc
|
|
RKObjectMapping* articleMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Article class]];
|
|
[RKObjectManager loadObjectsAtResourcePath:@"/objects" objectMapping:articleMapping delegate:self];
|
|
```
|
|
|
|
### Using Object Mapping Block Helpers
|
|
```objc
|
|
[[RKObjectManager sharedManager] postObject:user delegate:self block:^(RKObjectLoader* loader) {
|
|
loader.objectMapping = [RKObjectMapping mappingForClass:[User class] block:^(RKObjectMapping* mapping) {
|
|
mapping.rootKeyPath = @"user";
|
|
[mapping mapAttributes:@"password", nil];
|
|
[mapping mapKeyPath:@"passwordConfirmation" toAttribute:@"password_confirmation"];
|
|
}];
|
|
}];
|
|
```
|
|
|
|
### Configuring the Serialization Format
|
|
```objc
|
|
// Serialize to Form Encoded
|
|
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeFormURLEncoded;
|
|
|
|
// Serialize to JSON
|
|
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeJSON;
|
|
```
|
|
|
|
### Object Serialization Tasks
|
|
This is handled for you when using postObject and putObject, presented here for reference
|
|
|
|
```objc
|
|
RKUser* user = [User new];
|
|
user.firstName = @"Blake";
|
|
user.lastName = @"Watters";
|
|
|
|
RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[NSDictionary class]];
|
|
[mapping mapAttributes:@"firstName", @"lastName", nil];
|
|
|
|
RKObjectSerializer* serializer = [RKObjectSerializer serializerWithObject:object mapping:serializationMapping];
|
|
NSError* error = nil;
|
|
|
|
// Turn an object into a dictionary
|
|
NSMutableDictionary* dictionary = [serializer serializedObject:&error];
|
|
|
|
// Serialize the object to JSON
|
|
NSString* JSON = [serializer serializedObjectForMIMEType:RKMIMETypeJSON error:&error];
|
|
|
|
// Turn it into a RKRequestSerializable
|
|
id<RKRequestSerializable>serializable = [serializer serializationForMIMEType:RKMIMETypeJSON error:&error];
|
|
```
|
|
|
|
### Performing a Mapping
|
|
This is handled for you when using loadObjectAtResourcePath:, presented here for reference:
|
|
|
|
```objc
|
|
NSString* JSONString = @"{ \"name\": \"The name\", \"number\": 12345}";
|
|
NSString* MIMEType = @"application/json";
|
|
NSError* error = nil;
|
|
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:MIMEType];
|
|
id parsedData = [parser objectFromString:JSONString error:error];
|
|
if (parsedData == nil && error) {
|
|
// Parser error...
|
|
}
|
|
|
|
RKObjectMappingProvider* mappingProvider = [RKObjectManager sharedManager].mappingProvider;
|
|
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
|
|
RKObjectMappingResult* result = [mapper performMapping];
|
|
if (result) {
|
|
// Yay! Mapping finished successfully
|
|
}
|
|
```
|
|
|
|
### Registering a Parser
|
|
```objc
|
|
[[RKParserRegistry sharedRegistry] setParserClass:[RKJSONParserJSONKit class] forMIMEType:RKMIMETypeJSON];
|
|
```
|