Refactored object loaders to provide easier support for non-nested JSON responses. There are new flavors of getObject:, postObject:, etc.

that allow the developer to explicitly specify the object mapping to use for processing the response. closes #168
This commit is contained in:
Blake Watters
2011-06-30 09:37:53 -04:00
parent e18c1f2811
commit 2ac45f5ccc
12 changed files with 188 additions and 79 deletions

View File

@@ -77,12 +77,26 @@
*/
@interface RKObjectLoader : RKRequest {
RKObjectManager* _objectManager;
RKResponse* _response;
RKResponse* _response;
RKObjectMapping* _objectMapping;
RKObjectMappingResult* _result;
RKObjectMappingResult* _result;
RKObjectMapping* _serializationMapping;
NSString* _serializationMIMEType;
NSObject* _targetObject;
}
/**
* The object mapping to use when processing the response. If this is nil,
* then RestKit will search the parsed response body for mappable keyPaths and
* perform mapping on all available content. For instances where your target JSON
* is not returned under a uniquely identifiable keyPath, you must specify the object
* mapping directly for RestKit to know how to map it.
*
* @default nil
* @see RKObjectMappingProvider
*/
@property (nonatomic, retain) RKObjectMapping* objectMapping;
/**
* The object manager that initialized this loader. The object manager is responsible
* for supplying the mapper and object store used after HTTP transport is completed
@@ -95,9 +109,31 @@
@property (nonatomic, readonly) RKResponse* response;
/**
* The object mapping to apply to the response
* The mapping result that was produced after the request finished loading and
* object mapping has completed. Provides access to the final products of the
* object mapper in a variety of formats.
*/
@property (nonatomic, retain) RKObjectMapping* objectMapping;
@property (nonatomic, readonly) RKObjectMappingResult* result;
///////////////////////////////////////////////////////////////////////////////////////////
// Serialization
/**
* The object mapping to use when serializing a target object for transport
* to the remote server.
*
* @see RKObjectMappingProvider
*/
@property (nonatomic, retain) RKObjectMapping* serializationMapping;
/**
* The MIME Type to serialize the targetObject into according to the mapping
* rules in the serializationMapping. Typical MIME Types for serialization are
* JSON (RKMIMETypeJSON) and URL Form Encoded (RKMIMETypeFormURLEncoded).
*
* @see RKMIMEType
*/
@property (nonatomic, retain) NSString* serializationMIMEType;
/**
* The mappable object that generated this loader. This is used to map object
@@ -105,10 +141,7 @@
*/
@property (nonatomic, retain) NSObject* targetObject;
/**
* If the request was sent synchronously, this is how you get at the object mapping result.
*/
@property (nonatomic, retain) RKObjectMappingResult* result;
///////////////////////////////////////////////////////////////////////////////////////////
/**
* Initialize and return an object loader for a resource path against an object manager. The resource path
@@ -118,9 +151,10 @@
+ (id)loaderWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
* Initialize a new object loader with an object mapper, a request, and a delegate
* Initialize a new object loader with an object manager, a request, and a delegate
*/
- (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
* Handle an error in the response preventing it from being mapped, called from -isResponseMappable
*/

View File

@@ -16,6 +16,7 @@
#import "RKObjectLoader_Internals.h"
#import "RKParserRegistry.h"
#import "../Network/RKRequest_Internals.h"
#import "RKObjectSerializer.h"
// Set Logging Component
#undef RKLogComponent
@@ -26,6 +27,8 @@
@synthesize objectManager = _objectManager, response = _response;
@synthesize targetObject = _targetObject, objectMapping = _objectMapping;
@synthesize result = _result;
@synthesize serializationMapping = _serializationMapping;
@synthesize serializationMIMEType = _serializationMIMEType;
+ (id)loaderWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)delegate {
return [[[self alloc] initWithResourcePath:resourcePath objectManager:objectManager delegate:delegate] autorelease];
@@ -52,6 +55,8 @@
_objectMapping = nil;
[_result release];
_result = nil;
[_serializationMIMEType release];
[_serializationMapping release];
[super dealloc];
}
@@ -64,6 +69,8 @@
#pragma mark - Response Processing
// NOTE: This method is significant because the notifications posted are used by
// RKRequestQueue to remove requests from the queue. All requests need to be finalized.
- (void)finalizeLoad:(BOOL)successful error:(NSError*)error {
_isLoading = NO;
@@ -167,9 +174,11 @@
- (RKObjectMappingResult*)performMapping:(NSError**)error {
RKObjectMappingProvider* mappingProvider;
if (self.objectMapping) {
RKLogDebug(@"Found directly configured object mapping, creating temporary mapping provider...");
mappingProvider = [[RKObjectMappingProvider new] autorelease];
[mappingProvider setObjectMapping:self.objectMapping forKeyPath:@""];
} else {
RKLogDebug(@"No object mapping provider, using mapping provider from parent object manager to perform KVC mapping");
mappingProvider = self.objectManager.mappingProvider;
}
@@ -181,7 +190,7 @@
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSError* error = nil;
self.result = [self performMapping:&error];
_result = [[self performMapping:&error] retain];
if (self.result) {
[self processMappingResult:self.result];
} else {
@@ -221,6 +230,8 @@
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
}
// NOTE: We skip didFailLoadWithError: here so that we don't send the delegate
// conflicting messages around unexpected response and failure with error
[self finalizeLoad:NO error:error];
return NO;
@@ -250,7 +261,23 @@
#pragma mark - RKRequest & RKRequestDelegate methods
// Invoked just before request hits the network
- (void)prepareURLRequest {
- (BOOL)prepareURLRequest {
if (self.targetObject && (self.method == RKRequestMethodPOST || self.method == RKRequestMethodPUT)) {
NSAssert(self.serializationMapping, @"Cannot send an object to the remote");
RKLogDebug(@"POST or PUT request for target object %@, serializing to MIME Type %@ for transport...", self.targetObject, self.serializationMIMEType);
RKObjectSerializer* serializer = [RKObjectSerializer serializerWithObject:self.targetObject mapping:self.serializationMapping];
NSError* error = nil;
id params = [serializer serializationForMIMEType:self.serializationMIMEType error:&error];
if (error) {
RKLogError(@"Serializing failed for target object %@ to MIME Type %@: %@", self.targetObject, self.serializationMIMEType, [error localizedDescription]);
[self didFailLoadWithError:error];
return NO;
}
self.params = params;
}
// TODO: This is an informal protocol ATM. Maybe its not obvious enough?
if (self.targetObject) {
if ([self.targetObject respondsToSelector:@selector(willSendWithObjectLoader:)]) {
@@ -258,7 +285,7 @@
}
}
[super prepareURLRequest];
return [super prepareURLRequest];
}
- (void)didFailLoadWithError:(NSError*)error {
@@ -303,7 +330,7 @@
// Determine if we are synchronous here or not.
if (_sentSynchronously) {
NSError* error = nil;
self.result = [self performMapping:&error];
_result = [[self performMapping:&error] retain];
if (self.result) {
[self processMappingResult:self.result];
} else {

View File

@@ -183,28 +183,16 @@ typedef enum {
*/
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
Create and send an asynchronous GET request to load the objects at the specified resource path with a dictionary
of query parameters to append to the URL and call back the delegate with the loaded objects. Remote objects will be mapped
to local objects by consulting the keyPath registrations set on the mapping provider.
These methods have been deprecated. You can use [resourcePath appendQueryParams:queryParams] to achieve the same effect.
@deprecated
*/
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString *)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id<RKObjectLoaderDelegate>)delegate DEPRECATED_ATTRIBUTE;
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString *)resourcePath queryParams:(NSDictionary*)queryParams objectMapping:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate DEPRECATED_ATTRIBUTE;
/**
Load mappable objects at the specified resourcePath using the specified object mapping.
*/
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath objectMapping:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate;
////////////////////////////////////////////////////////
/// @name Mappable object helpers
/// @name Mappable Object Loaders
/**
Update a mappable model by loading its attributes from the web
Fetch the data for a mappable object by performing an HTTP GET.
*/
- (RKObjectLoader*)getObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate;
@@ -223,8 +211,31 @@ typedef enum {
*/
- (RKObjectLoader*)deleteObject:(id<NSObject>)object delegate:(id<RKObjectLoaderDelegate>)delegate;
////////////////////////////////////////////////////////
/// @name Object Loader Primitives
//////
/**
Fetch the data for a mappable object by performing an HTTP GET. The data returned in the response will be mapped according
to the object mapping provided.
*/
- (RKObjectLoader*)getObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
Send the data for a mappable object by performing an HTTP POST. The data returned in the response will be mapped according
to the object mapping provided.
*/
- (RKObjectLoader*)postObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
Send the data for a mappable object by performing an HTTP PUT. The data returned in the response will be mapped according
to the object mapping provided.
*/
- (RKObjectLoader*)putObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
Delete a remote object representation by performing an HTTP DELETE. The data returned in the response will be mapped according
to the object mapping provided.
*/
- (RKObjectLoader*)deleteObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate;
/**
These methods are provided for situations where the remote system you are working with has slightly different conventions

View File

@@ -10,7 +10,6 @@
#import "RKObjectSerializer.h"
#import "../CoreData/RKManagedObjectStore.h"
#import "../CoreData/RKManagedObjectLoader.h"
//#import "../Support/RKMIMETypes.h"
#import "../Support/Support.h"
#import "RKErrorMessage.h"
@@ -142,16 +141,6 @@ static RKObjectManager* sharedManager = nil;
return loader;
}
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString *)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id<RKObjectLoaderDelegate>)delegate {
NSString* resourcePathWithQuery = RKPathAppendQueryParams(resourcePath, queryParams);
RKObjectLoader* loader = [self objectLoaderWithResourcePath:resourcePathWithQuery delegate:delegate];
loader.method = RKRequestMethodGET;
[loader send];
return loader;
}
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath objectMapping:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
RKObjectLoader* loader = [self objectLoaderWithResourcePath:resourcePath delegate:delegate];
loader.method = RKRequestMethodGET;
@@ -162,17 +151,6 @@ static RKObjectManager* sharedManager = nil;
return loader;
}
- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString *)resourcePath queryParams:(NSDictionary*)queryParams objectMapping:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
NSString* resourcePathWithQuery = RKPathAppendQueryParams(resourcePath, queryParams);
RKObjectLoader* loader = [self objectLoaderWithResourcePath:resourcePathWithQuery delegate:delegate];
loader.method = RKRequestMethodGET;
loader.objectMapping = objectMapping;
[loader send];
return loader;
}
/////////////////////////////////////////////////////////////
#pragma mark - Object Instance Loaders
@@ -181,20 +159,8 @@ static RKObjectManager* sharedManager = nil;
RKObjectLoader* loader = [self objectLoaderWithResourcePath:resourcePath delegate:delegate];
loader.method = method;
loader.targetObject = object;
if (method == RKRequestMethodPOST || method == RKRequestMethodPUT) {
RKObjectMapping* serializationMapping = [self.mappingProvider serializationMappingForClass:[object class]];
RKObjectSerializer* serializer = [RKObjectSerializer serializerWithObject:object mapping:serializationMapping];
NSError* error = nil;
id params = [serializer serializationForMIMEType:self.serializationMIMEType error:&error];
if (error) {
[delegate objectLoader:loader didFailWithError:error];
return nil;
}
loader.params = params;
}
loader.serializationMIMEType = self.serializationMIMEType;
loader.serializationMapping = [self.mappingProvider serializationMappingForClass:[object class]];
return loader;
}
@@ -223,4 +189,35 @@ static RKObjectManager* sharedManager = nil;
return loader;
}
#pragma mark - Object Instance Loaders for Non-nested JSON
- (RKObjectLoader*)getObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
RKObjectLoader* loader = [self objectLoaderForObject:object method:RKRequestMethodGET delegate:delegate];
loader.objectMapping = objectMapping;
[loader send];
return loader;
}
- (RKObjectLoader*)postObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
RKObjectLoader* loader = [self objectLoaderForObject:object method:RKRequestMethodPOST delegate:delegate];
loader.objectMapping = objectMapping;
[loader send];
return loader;
}
- (RKObjectLoader*)putObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
RKObjectLoader* loader = [self objectLoaderForObject:object method:RKRequestMethodPUT delegate:delegate];
loader.objectMapping = objectMapping;
[loader send];
return loader;
}
- (RKObjectLoader*)deleteObject:(id<NSObject>)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id<RKObjectLoaderDelegate>)delegate {
RKObjectLoader* loader = [self objectLoaderForObject:object method:RKRequestMethodDELETE delegate:delegate];
loader.objectMapping = objectMapping;
[loader send];
return loader;
}
@end