diff --git a/Code/Network/RKRequest.h b/Code/Network/RKRequest.h index 203de80b..e692dd6f 100644 --- a/Code/Network/RKRequest.h +++ b/Code/Network/RKRequest.h @@ -32,7 +32,8 @@ * HTTP methods for requests */ typedef enum RKRequestMethod { - RKRequestMethodGET = 0, + RKRequestMethodInvalid = -1, + RKRequestMethodGET, RKRequestMethodPOST, RKRequestMethodPUT, RKRequestMethodDELETE, @@ -92,6 +93,10 @@ typedef enum { @class RKResponse, RKRequestQueue, RKReachabilityObserver; @protocol RKRequestDelegate, RKConfigurationDelegate; +/** @name Block Handlers */ +typedef void(^RKRequestDidLoadResponseBlock)(RKResponse *response); +typedef void(^RKRequestDidFailLoadWithErrorBlock)(NSError *error); + /** Models the request portion of an HTTP request/response cycle. */ @@ -160,6 +165,20 @@ typedef enum { */ @property(nonatomic, assign) id delegate; +/** + A block to invoke when the receuver has loaded a response. + + @see [RKRequestDelegate request:didLoadResponse:] + */ +@property(nonatomic, copy) RKRequestDidLoadResponseBlock onDidLoadResponse; + +/** + A block to invoke when the receuver has failed loading due to an error. + + @see [RKRequestDelegate request:didFailLoadWithError:] + */ +@property(nonatomic, copy) RKRequestDidFailLoadWithErrorBlock onDidFailLoadWithError; + /** A delegate responsible for configuring the request. Centralizes common configuration data (such as HTTP headers, authentication information, etc) for re-use. diff --git a/Code/Network/RKRequest.m b/Code/Network/RKRequest.m index d01c8bd2..8007175b 100644 --- a/Code/Network/RKRequest.m +++ b/Code/Network/RKRequest.m @@ -64,6 +64,8 @@ @synthesize timeoutInterval = _timeoutInterval; @synthesize reachabilityObserver = _reachabilityObserver; @synthesize configurationDelegate = _configurationDelegate; +@synthesize onDidLoadResponse; +@synthesize onDidFailLoadWithError; #if TARGET_OS_IPHONE @synthesize backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier; @@ -167,6 +169,10 @@ _OAuth2AccessToken = nil; [_OAuth2RefreshToken release]; _OAuth2RefreshToken = nil; + [onDidFailLoadWithError release]; + onDidFailLoadWithError = nil; + [onDidLoadResponse release]; + onDidLoadResponse = nil; [self invalidateTimeoutTimer]; [_timeoutTimer release]; _timeoutTimer = nil; @@ -565,6 +571,10 @@ [_delegate request:self didFailLoadWithError:error]; } + if (self.onDidFailLoadWithError) { + self.onDidFailLoadWithError(error); + } + NSDictionary* userInfo = [NSDictionary dictionaryWithObject:error forKey:RKRequestDidFailWithErrorNotificationUserInfoErrorKey]; [[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidFailWithErrorNotification object:self @@ -600,6 +610,10 @@ [_delegate request:self didLoadResponse:finalResponse]; } + if (self.onDidLoadResponse) { + self.onDidLoadResponse(finalResponse); + } + if ([response isServiceUnavailable]) { [[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self]; } diff --git a/Code/ObjectMapping/RKObjectLoader.h b/Code/ObjectMapping/RKObjectLoader.h index 83014a29..ac4e13a9 100644 --- a/Code/ObjectMapping/RKObjectLoader.h +++ b/Code/ObjectMapping/RKObjectLoader.h @@ -25,6 +25,22 @@ @class RKObjectMappingProvider; @class RKObjectLoader; +// Block Types +typedef void(^RKObjectLoaderBlock)(RKObjectLoader *loader); +typedef void(^RKObjectLoaderDidFailWithErrorBlock)(NSError *error); +typedef void(^RKObjectLoaderDidLoadObjectsBlock)(NSArray *objects); +typedef void(^RKObjectLoaderDidLoadObjectBlock)(id object); +typedef void(^RKObjectLoaderDidLoadObjectsDictionaryBlock)(NSDictionary *dictionary); + +/** + The delegate of an RKObjectLoader object must adopt the RKObjectLoaderDelegate protocol. Optional + methods of the protocol allow the delegate to handle asynchronous object mapping operations performed + by the object loader. Also note that the RKObjectLoaderDelegate protocol incorporates the + RKRequestDelegate protocol and the delegate may provide implementations of methods from RKRequestDelegate + as well. + + @see RKRequestDelegate + */ @protocol RKObjectLoaderDelegate @required @@ -32,7 +48,7 @@ /** * Sent when an object loaded failed to load the collection due to an error */ -- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error; +- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error; @optional @@ -41,7 +57,7 @@ and loaded a collection of objects. All objects mapped from the remote payload will be returned as a single array. */ -- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects; +- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects; /** When implemented, sent to the delegate when the object loader has completed succesfully. @@ -49,19 +65,19 @@ in the collection will be sent with this delegate method. This method simplifies things when you know you are working with a single object reference. */ -- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObject:(id)object; +- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObject:(id)object; /** When implemented, sent to the delegate when an object loader has completed successfully. The dictionary will be expressed as pairs of keyPaths and objects mapped from the payload. This method is useful when you have multiple root objects and want to differentiate them by keyPath. */ -- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjectDictionary:(NSDictionary*)dictionary; +- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjectDictionary:(NSDictionary *)dictionary; /** Invoked when the object loader has finished loading */ -- (void)objectLoaderDidFinishLoading:(RKObjectLoader*)objectLoader; +- (void)objectLoaderDidFinishLoading:(RKObjectLoader *)objectLoader; /** Sent when an object loader encounters a response status code or MIME Type that RestKit does not know how to handle. @@ -86,7 +102,7 @@ @optional */ -- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader*)objectLoader; +- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader *)objectLoader; /** Invoked just after parsing has completed, but before object mapping begins. This can be helpful @@ -97,7 +113,7 @@ Note that the mappable data is a pointer to a pointer to allow you to replace the mappable data with a new object to be mapped. You must dereference it to access the value. */ -- (void)objectLoader:(RKObjectLoader*)loader willMapData:(inout id *)mappableData; +- (void)objectLoader:(RKObjectLoader *)loader willMapData:(inout id *)mappableData; @end @@ -119,6 +135,49 @@ NSObject* _targetObject; } +/** + The object that acts as the delegate of the receiving object loader. + + @see RKRequestDelegate + */ +@property (nonatomic, assign) id delegate; + +/** + The block to invoke when the object loader fails due to an error. + + @see [RKObjectLoaderDelegate objectLoader:didFailWithError:] + */ +@property (nonatomic, copy) RKObjectLoaderDidFailWithErrorBlock onDidFailWithError; + +/** + The block to invoke when the object loader has completed object mapping and the consumer + wishes to retrieve a single object from the mapping result. + + @see [RKObjectLoaderDelegate objectLoader:didLoadObject:] + @see RKObjectMappingResult + */ +@property (nonatomic, copy) RKObjectLoaderDidLoadObjectBlock onDidLoadObject; + +/** + The block to invoke when the object loader has completed object mapping and the consumer + wishes to retrieve an collections of objects from the mapping result. + + @see [RKObjectLoaderDelegate objectLoader:didLoadObjects:] + @see RKObjectMappingResult + */ +@property (nonatomic, copy) RKObjectLoaderDidLoadObjectsBlock onDidLoadObjects; + +/** + The block to invoke when the object loader has completed object mapping and the consumer + wishes to retrieve the entire mapping result as a dictionary. Each key within the + dictionary will correspond to a mapped keyPath within the source JSON/XML and the value + will be the object mapped result. + + @see [RKObjectLoaderDelegate objectLoader:didLoadObjects:] + @see RKObjectMappingResult + */ +@property (nonatomic, copy) RKObjectLoaderDidLoadObjectsDictionaryBlock onDidLoadObjectsDictionary; + /** * 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 @@ -129,7 +188,7 @@ * @default nil * @see RKObjectMappingProvider */ -@property (nonatomic, retain) RKObjectMapping* objectMapping; +@property (nonatomic, retain) RKObjectMapping *objectMapping; /** A mapping provider containing object mapping configurations for mapping remote @@ -142,14 +201,14 @@ /** * The underlying response object for this loader */ -@property (nonatomic, readonly) RKResponse* response; +@property (nonatomic, readonly) RKResponse *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, readonly) RKObjectMappingResult* result; +@property (nonatomic, readonly) RKObjectMappingResult *result; /////////////////////////////////////////////////////////////////////////////////////////// // Serialization @@ -160,7 +219,7 @@ * * @see RKObjectMappingProvider */ -@property (nonatomic, retain) RKObjectMapping* serializationMapping; +@property (nonatomic, retain) RKObjectMapping *serializationMapping; /** * The MIME Type to serialize the targetObject into according to the mapping @@ -169,7 +228,7 @@ * * @see RKMIMEType */ -@property (nonatomic, retain) NSString* serializationMIMEType; +@property (nonatomic, retain) NSString *serializationMIMEType; /** The object being serialized for transport. This object will be transformed into a @@ -177,14 +236,14 @@ @see RKObjectSerializer */ -@property (nonatomic, retain) NSObject* sourceObject; +@property (nonatomic, retain) NSObject *sourceObject; /** * The target object to map results back onto. If nil, a new object instance * for the appropriate mapping will be created. If not nil, the results will * be used to update the targetObject's attributes and relationships. */ -@property (nonatomic, retain) NSObject* targetObject; +@property (nonatomic, retain) NSObject *targetObject; /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/ObjectMapping/RKObjectLoader.m b/Code/ObjectMapping/RKObjectLoader.m index 4154e5af..18ea48a6 100644 --- a/Code/ObjectMapping/RKObjectLoader.m +++ b/Code/ObjectMapping/RKObjectLoader.m @@ -47,6 +47,10 @@ @synthesize serializationMapping = _serializationMapping; @synthesize serializationMIMEType = _serializationMIMEType; @synthesize sourceObject = _sourceObject; +@synthesize onDidFailWithError; +@synthesize onDidLoadObject; +@synthesize onDidLoadObjects; +@synthesize onDidLoadObjectsDictionary; + (id)loaderWithURL:(RKURL *)URL mappingProvider:(RKObjectMappingProvider *)mappingProvider { return [[[self alloc] initWithURL:URL mappingProvider:mappingProvider] autorelease]; @@ -75,7 +79,17 @@ [_result release]; _result = nil; [_serializationMIMEType release]; + _serializationMIMEType = nil; [_serializationMapping release]; + _serializationMapping = nil; + [onDidFailWithError release]; + onDidFailWithError = nil; + [onDidLoadObject release]; + onDidLoadObject = nil; + [onDidLoadObjects release]; + onDidLoadObjects = nil; + [onDidLoadObjectsDictionary release]; + onDidLoadObjectsDictionary = nil; [super dealloc]; } @@ -88,6 +102,14 @@ _result = nil; } +- (void)informDelegateOfError:(NSError *)error { + [(NSObject*)_delegate objectLoader:self didFailWithError:error]; + + if (self.onDidFailWithError) { + self.onDidFailWithError(error); + } +} + #pragma mark - Response Processing // NOTE: This method is significant because the notifications posted are used by @@ -121,19 +143,34 @@ NSAssert([NSThread isMainThread], @"RKObjectLoaderDelegate callbacks must occur on the main thread"); RKObjectMappingResult* result = [RKObjectMappingResult mappingResultWithDictionary:resultDictionary]; - + + // Dictionary callback if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObjectDictionary:)]) { [(NSObject*)self.delegate objectLoader:self didLoadObjectDictionary:[result asDictionary]]; } + if (self.onDidLoadObjectsDictionary) { + self.onDidLoadObjectsDictionary([result asDictionary]); + } + + // Collection callback if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObjects:)]) { [(NSObject*)self.delegate objectLoader:self didLoadObjects:[result asCollection]]; } + if (self.onDidLoadObjects) { + self.onDidLoadObjects([result asCollection]); + } + + // Singular object callback if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObject:)]) { [(NSObject*)self.delegate objectLoader:self didLoadObject:[result asObject]]; } + if (self.onDidLoadObject) { + self.onDidLoadObject([result asObject]); + } + [self finalizeLoad:YES error:nil]; } @@ -257,7 +294,7 @@ } if ([self.response isFailure]) { - [(NSObject*)_delegate objectLoader:self didFailWithError:self.response.failureError]; + [self informDelegateOfError:self.response.failureError]; [self finalizeLoad:NO error:self.response.failureError]; @@ -272,8 +309,8 @@ NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKObjectLoaderUnexpectedResponseError userInfo:nil]; if ([_delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) { [(NSObject*)_delegate objectLoaderDidLoadUnexpectedResponse:self]; - } else { - [(NSObject*)_delegate objectLoader:self didFailWithError:error]; + } else { + [self informDelegateOfError:error]; } // NOTE: We skip didFailLoadWithError: here so that we don't send the delegate @@ -301,7 +338,7 @@ RKLogError(@"Encountered an error while attempting to map server side errors from payload: %@", [error localizedDescription]); } - [(NSObject*)_delegate objectLoader:self didFailWithError:error]; + [self informDelegateOfError:error]; [self finalizeLoad:NO error:error]; } @@ -347,8 +384,7 @@ [_delegate request:self didFailLoadWithError:error]; } - [(NSObject*)_delegate objectLoader:self didFailWithError:error]; - + [self informDelegateOfError:error]; [self finalizeLoad:NO error:error]; } @@ -391,6 +427,16 @@ } } +// Proxy the delegate property back to our superclass implementation. The object loader should +// really not be a subclass of RKRequest. +- (void)setDelegate:(id)delegate { + [super setDelegate:delegate]; +} + +- (id)delegate { + return (id) [super delegate]; +} + @end @implementation RKObjectLoader (Deprecations) @@ -399,10 +445,10 @@ return [[[self alloc] initWithResourcePath:resourcePath objectManager:objectManager delegate:delegate] autorelease]; } -- (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id)delegate { +- (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id)theDelegate { if ((self = [self initWithURL:[objectManager.baseURL URLByAppendingResourcePath:resourcePath] mappingProvider:objectManager.mappingProvider])) { [objectManager.client configureRequest:self]; - _delegate = delegate; + _delegate = theDelegate; } return self; diff --git a/Code/ObjectMapping/RKObjectManager.h b/Code/ObjectMapping/RKObjectManager.h index 4522a479..b3d6f1c8 100644 --- a/Code/ObjectMapping/RKObjectManager.h +++ b/Code/ObjectMapping/RKObjectManager.h @@ -122,12 +122,12 @@ typedef enum { /** Return the shared instance of the object manager */ -+ (RKObjectManager*)sharedManager; ++ (RKObjectManager *)sharedManager; /** Set the shared instance of the object manager */ -+ (void)setSharedManager:(RKObjectManager*)manager; ++ (void)setSharedManager:(RKObjectManager *)manager; /// @name Initializing an Object Manager @@ -135,8 +135,8 @@ typedef enum { Create and initialize a new object manager. If this is the first instance created it will be set as the shared instance */ -+ (RKObjectManager*)managerWithBaseURLString:(NSString *)baseURLString; -+ (RKObjectManager*)managerWithBaseURL:(NSURL *)baseURL; ++ (id)managerWithBaseURLString:(NSString *)baseURLString; ++ (id)managerWithBaseURL:(NSURL *)baseURL; /** Initializes a newly created object manager with a specified baseURL. @@ -151,7 +151,7 @@ typedef enum { /** The underlying HTTP client for this manager */ -@property (nonatomic, retain) RKClient* client; +@property (nonatomic, retain) RKClient *client; /** The base URL of the underlying RKClient instance. Object loader @@ -185,191 +185,36 @@ typedef enum { /** The Mapping Provider responsible for returning mappings for various keyPaths. */ -@property (nonatomic, retain) RKObjectMappingProvider* mappingProvider; +@property (nonatomic, retain) RKObjectMappingProvider *mappingProvider; /** Router object responsible for generating resource paths for HTTP requests */ -@property (nonatomic, retain) RKObjectRouter* router; +@property (nonatomic, retain) RKObjectRouter *router; /** A Core Data backed object store for persisting objects that have been fetched from the Web */ -@property (nonatomic, retain) RKManagedObjectStore* objectStore; +@property (nonatomic, retain) RKManagedObjectStore *objectStore; /** The Default MIME Type to be used in object serialization. */ -@property (nonatomic, retain) NSString* serializationMIMEType; +@property (nonatomic, retain) NSString *serializationMIMEType; /** The value for the HTTP Accept header to specify the preferred format for retrieved data */ -@property (nonatomic, assign) NSString* acceptMIMEType; - -/** - When YES, RestKit will auto-select the appropriate object mapping for a particular object - passed through getObject:, postObject:, putObject:, and deleteObject:. - - This is useful when you are working with mappable data that is not identifiable via KVC - and you are sending/receiving objects of the same type. When YES, RestKit will search the - mappingProvider for an object mapping targeting the same type of object that you passed into - getObject:, postObject:, :putObject, or deleteObject: and configure the RKObjectLoader to map - the payload using that mapping. This is merely a convenience for users who are working entirely - with non-KVC mappable data and saves the added step of searching the mapping provider manually. - - Default: NO - */ -@property (nonatomic, assign) BOOL inferMappingsFromObjectTypes; +@property (nonatomic, assign) NSString *acceptMIMEType; //////////////////////////////////////////////////////// -/// @name Registered Object Loaders +/// @name Building Object Loaders /** - These methods are suitable for loading remote payloads that encode type information into the payload. This enables - the mapping of complex payloads spanning multiple types (i.e. a search operation returning Articles & Comments in - one payload). Ruby on Rails JSON serialization is an example of such a conformant system. - */ - -/** - Create and send an asynchronous GET request to load the objects at the resource path 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. - */ -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath delegate:(id)delegate; - -/** - Load mappable objects at the specified resourcePath using the specified object mapping. - */ -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath objectMapping:(RKObjectMapping*)objectMapping delegate:(id)delegate; - -//////////////////////////////////////////////////////// -/// @name Mappable Object Loaders - -/** - Fetch the data for a mappable object by performing an HTTP GET. - */ -- (RKObjectLoader*)getObject:(id)object delegate:(id)delegate; - -/** - Create a remote mappable model by POSTing the attributes to the remote resource and loading the resulting objects from the payload - */ -- (RKObjectLoader*)postObject:(id)object delegate:(id)delegate; - -/** - Update a remote mappable model by PUTing the attributes to the remote resource and loading the resulting objects from the payload - */ -- (RKObjectLoader*)putObject:(id)object delegate:(id)delegate; - -/** - Delete the remote instance of a mappable model by performing an HTTP DELETE on the remote resource - */ -- (RKObjectLoader*)deleteObject:(id)object delegate:(id)delegate; - -//////////////////////////////////////////////////////// -/// @name Block Configured Object Loaders - -#if NS_BLOCKS_AVAILABLE - -/** - Load the objects at the specified resource path and perform object mapping on the response payload. Prior to sending the object loader, the - block will be invoked to allow you to configure the object loader as you see fit. This can be used to change the response type, set custom - parameters, choose an object mapping, etc. - - For example: - - - (void)loadObjectWithBlockExample { - [[RKObjectManager sharedManager] loadObjectsAtResourcePath:@"/monkeys.json" delegate:self block:^(RKObjectLoader* loader) { - loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Monkey class]]; - }]; - } - */ -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -/** - Configure and send an object loader after yielding it to a block for configuration. This allows for very succinct on-the-fly - configuration of the request without obtaining an object reference via objectLoaderForObject: and then sending it yourself. - - For example: - - - (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error { - if ([self validatePassword:newPassword error:error]) { - self.password = newPassword; - [[RKObjectManager sharedManager] sendObject:self delegate:self block:^(RKObjectLoader* loader) { - loader.method = RKRequestMethodPOST; - loader.serializationMIMEType = RKMIMETypeJSON; // We want to send this request as JSON - loader.targetObject = nil; // Map the results back onto a new object instead of self - // Set up a custom serialization mapping to handle this request - loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) { - [mapping mapAttributes:@"password", nil]; - }]; - }]; - } - } - */ -- (RKObjectLoader*)sendObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -/** - GET a remote object instance and yield the object loader to the block before sending - - @see sendObject:method:delegate:block - */ -- (RKObjectLoader*)getObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -/** - POST a remote object instance and yield the object loader to the block before sending - - @see sendObject:method:delegate:block - - (RKObjectLoader*)postObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - */ -- (RKObjectLoader*)postObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -/** - PUT a remote object instance and yield the object loader to the block before sending - - @see sendObject:method:delegate:block - */ -- (RKObjectLoader*)putObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -/** - DELETE a remote object instance and yield the object loader to the block before sending - - @see sendObject:method:delegate:block - */ -- (RKObjectLoader*)deleteObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; - -#endif - -////// - -/** - 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)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)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)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)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)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)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)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)delegate; - -/** - Return the class of object loader instances built through the manager. When Core Data has - been configured + Returns the class of object loader instances built through the manager. When Core Data has + been configured, instances of RKManagedObjectLoader will be emitted by the manager. Otherwise + RKObjectLoader is used. @return RKObjectLoader OR RKManagedObjectLoader */ @@ -429,10 +274,140 @@ typedef enum { */ - (RKObjectPaginator *)paginatorWithResourcePathPattern:(NSString *)resourcePathPattern; +//////////////////////////////////////////////////////// +/// @name Registered Object Loaders -+ (RKObjectManager*)objectManagerWithBaseURLString:(NSString *)baseURLString; -+ (RKObjectManager*)objectManagerWithBaseURL:(NSURL *)baseURL; -- (RKObjectLoader*)objectLoaderWithResourcePath:(NSString*)resourcePath delegate:(id)delegate DEPRECATED_ATTRIBUTE; -- (RKObjectLoader*)objectLoaderForObject:(id)object method:(RKRequestMethod)method delegate:(id)delegate DEPRECATED_ATTRIBUTE; +/** + These methods are suitable for loading remote payloads that encode type information into the payload. This enables + the mapping of complex payloads spanning multiple types (i.e. a search operation returning Articles & Comments in + one payload). Ruby on Rails JSON serialization is an example of such a conformant system. + */ + +/** + Create and send an asynchronous GET request to load the objects at the resource path 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. + */ +- (void)loadObjectsAtResourcePath:(NSString *)resourcePath delegate:(id)delegate; + +//////////////////////////////////////////////////////// +/// @name Mappable Object Loaders + +/** + Fetch the data for a mappable object by performing an HTTP GET. + */ +- (void)getObject:(id)object delegate:(id)delegate; + +/** + Create a remote mappable model by POSTing the attributes to the remote resource and loading the resulting objects from the payload + */ +- (void)postObject:(id)object delegate:(id)delegate; + +/** + Update a remote mappable model by PUTing the attributes to the remote resource and loading the resulting objects from the payload + */ +- (void)putObject:(id)object delegate:(id)delegate; + +/** + Delete the remote instance of a mappable model by performing an HTTP DELETE on the remote resource + */ +- (void)deleteObject:(id)object delegate:(id)delegate; + +//////////////////////////////////////////////////////// +/// @name Block Configured Object Loaders + +#if NS_BLOCKS_AVAILABLE + +/** + Load the objects at the specified resource path and perform object mapping on the response payload. Prior to sending the object loader, the + block will be invoked to allow you to configure the object loader as you see fit. This can be used to change the response type, set custom + parameters, choose an object mapping, etc. + + For example: + + - (void)loadObjectWithBlockExample { + [[RKObjectManager sharedManager] loadObjectsAtResourcePath:@"/monkeys.json" delegate:self block:^(RKObjectLoader* loader) { + loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Monkey class]]; + }]; + } + */ +- (void)loadObjectsAtResourcePath:(NSString *)resourcePath usingBlock:(RKObjectLoaderBlock)block; + +/** + Configure and send an object loader after yielding it to a block for configuration. This allows for very succinct on-the-fly + configuration of the request without obtaining an object reference via objectLoaderForObject: and then sending it yourself. + + For example: + + - (BOOL)changePassword:(NSString*)newPassword error:(NSError**)error { + if ([self validatePassword:newPassword error:error]) { + self.password = newPassword; + [[RKObjectManager sharedManager] sendObject:self toResourcePath:@"/some/path" usingBlock:^(RKObjectLoader* loader) { + loader.delegate = self; + loader.method = RKRequestMethodPOST; + loader.serializationMIMEType = RKMIMETypeJSON; // We want to send this request as JSON + loader.targetObject = nil; // Map the results back onto a new object instead of self + // Set up a custom serialization mapping to handle this request + loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) { + [mapping mapAttributes:@"password", nil]; + }]; + }]; + } + } + */ +- (void)sendObject:(id)object toResourcePath:(NSString *)resourcePath usingBlock:(RKObjectLoaderBlock)block; + +/** + GET a remote object instance and yield the object loader to the block before sending + + @see sendObject:method:delegate:block + */ +- (void)getObject:(id)object usingBlock:(RKObjectLoaderBlock)block; + +/** + POST a remote object instance and yield the object loader to the block before sending + + @see sendObject:method:delegate:block + - (RKObjectLoader*)postObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block; + */ +- (void)postObject:(id)object usingBlock:(RKObjectLoaderBlock)block; + +/** + PUT a remote object instance and yield the object loader to the block before sending + + @see sendObject:method:delegate:block + */ +- (void)putObject:(id)object usingBlock:(RKObjectLoaderBlock)block; + +/** + DELETE a remote object instance and yield the object loader to the block before sending + + @see sendObject:method:delegate:block + */ +- (void)deleteObject:(id)object usingBlock:(RKObjectLoaderBlock)block; + +#endif + +////// + + +// Deprecations + ++ (RKObjectManager *)objectManagerWithBaseURLString:(NSString *)baseURLString; ++ (RKObjectManager *)objectManagerWithBaseURL:(NSURL *)baseURL; +- (void)loadObjectsAtResourcePath:(NSString*)resourcePath objectMapping:(RKObjectMapping*)objectMapping delegate:(id)delegate DEPRECATED_ATTRIBUTE; +- (RKObjectLoader *)objectLoaderWithResourcePath:(NSString*)resourcePath delegate:(id)delegate DEPRECATED_ATTRIBUTE; +- (RKObjectLoader *)objectLoaderForObject:(id)object method:(RKRequestMethod)method delegate:(id)delegate DEPRECATED_ATTRIBUTE; + +/* + NOTE: + + The mapResponseWith: family of methods have been deprecated by the support for object mapping selection + using resourcePath's + */ +- (void)getObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate DEPRECATED_ATTRIBUTE; +- (void)postObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate DEPRECATED_ATTRIBUTE; +- (void)putObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate DEPRECATED_ATTRIBUTE; +- (void)deleteObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate DEPRECATED_ATTRIBUTE; @end diff --git a/Code/ObjectMapping/RKObjectManager.m b/Code/ObjectMapping/RKObjectManager.m index e7eea561..08b5798e 100644 --- a/Code/ObjectMapping/RKObjectManager.m +++ b/Code/ObjectMapping/RKObjectManager.m @@ -25,13 +25,13 @@ #import "Support.h" #import "RKErrorMessage.h" -NSString* const RKDidEnterOfflineModeNotification = @"RKDidEnterOfflineModeNotification"; -NSString* const RKDidEnterOnlineModeNotification = @"RKDidEnterOnlineModeNotification"; +NSString * const RKDidEnterOfflineModeNotification = @"RKDidEnterOfflineModeNotification"; +NSString * const RKDidEnterOnlineModeNotification = @"RKDidEnterOnlineModeNotification"; ////////////////////////////////// // Shared Instance -static RKObjectManager* sharedManager = nil; +static RKObjectManager *sharedManager = nil; /////////////////////////////////// @@ -42,7 +42,6 @@ static RKObjectManager* sharedManager = nil; @synthesize router = _router; @synthesize mappingProvider = _mappingProvider; @synthesize serializationMIMEType = _serializationMIMEType; -@synthesize inferMappingsFromObjectTypes = _inferMappingsFromObjectTypes; - (id)initWithBaseURL:(RKURL *)baseURL { self = [super init]; @@ -51,13 +50,12 @@ static RKObjectManager* sharedManager = nil; _router = [RKObjectRouter new]; _client = [[RKClient alloc] initWithBaseURL:baseURL]; _onlineState = RKObjectManagerOnlineStateUndetermined; - _inferMappingsFromObjectTypes = NO; self.acceptMIMEType = RKMIMETypeJSON; self.serializationMIMEType = RKMIMETypeFormURLEncoded; // Setup default error message mappings - RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]]; + RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]]; errorMapping.rootKeyPath = @"errors"; [errorMapping mapKeyPath:@"" toAttribute:@"errorMessage"]; _mappingProvider.errorMapping = errorMapping; @@ -76,22 +74,22 @@ static RKObjectManager* sharedManager = nil; return self; } -+ (RKObjectManager*)sharedManager { ++ (RKObjectManager *)sharedManager { return sharedManager; } -+ (void)setSharedManager:(RKObjectManager*)manager { ++ (void)setSharedManager:(RKObjectManager *)manager { [manager retain]; [sharedManager release]; sharedManager = manager; } -+ (RKObjectManager*)managerWithBaseURLString:(NSString *)baseURLString { ++ (RKObjectManager *)managerWithBaseURLString:(NSString *)baseURLString { return [self managerWithBaseURL:[RKURL URLWithString:baseURLString]]; } -+ (RKObjectManager*)managerWithBaseURL:(NSURL *)baseURL { - RKObjectManager* manager = [[[self alloc] initWithBaseURL:baseURL] autorelease]; ++ (RKObjectManager *)managerWithBaseURL:(NSURL *)baseURL { + RKObjectManager *manager = [[[self alloc] initWithBaseURL:baseURL] autorelease]; return manager; } @@ -120,7 +118,7 @@ static RKObjectManager* sharedManager = nil; return ![self isOnline]; } -- (void)reachabilityChanged:(NSNotification*)notification { +- (void)reachabilityChanged:(NSNotification *)notification { BOOL isHostReachable = [self.client.reachabilityObserver isNetworkReachable]; _onlineState = isHostReachable ? RKObjectManagerOnlineStateConnected : RKObjectManagerOnlineStateDisconnected; @@ -132,11 +130,11 @@ static RKObjectManager* sharedManager = nil; } } -- (void)setAcceptMIMEType:(NSString*)MIMEType { +- (void)setAcceptMIMEType:(NSString *)MIMEType { [_client setValue:MIMEType forHTTPHeaderField:@"Accept"]; } -- (NSString*)acceptMIMEType { +- (NSString *)acceptMIMEType { return [self.client.HTTPHeaders valueForKey:@"Accept"]; } @@ -181,209 +179,136 @@ static RKObjectManager* sharedManager = nil; } - (id)loaderForObject:(id)object method:(RKRequestMethod)method { - NSString* resourcePath = [self.router resourcePathForObject:object method:method]; - RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; + NSString* resourcePath = (method == RKRequestMethodInvalid) ? nil : [self.router resourcePathForObject:object method:method]; + RKObjectLoader *loader = [self loaderWithResourcePath:resourcePath]; loader.method = method; loader.sourceObject = object; - loader.targetObject = object; loader.serializationMIMEType = self.serializationMIMEType; loader.serializationMapping = [self.mappingProvider serializationMappingForClass:[object class]]; - if (self.inferMappingsFromObjectTypes) { - RKObjectMapping* objectMapping = [self.mappingProvider objectMappingForClass:[object class]]; - RKLogDebug(@"Auto-selected object mapping %@ for object of type %@", objectMapping, NSStringFromClass([object class])); - loader.objectMapping = objectMapping; + RKObjectMapping *objectMapping = [self.mappingProvider objectMappingForResourcePath:resourcePath]; + if (objectMapping == nil || [object isMemberOfClass:[objectMapping objectClass]]) { + loader.targetObject = object; + } else { + loader.targetObject = nil; } return loader; } -- (RKObjectLoader*)objectLoaderWithResourcePath:(NSString*)resourcePath delegate:(id)delegate { - RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; - loader.delegate = delegate; - - return loader; -} - -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath delegate:(id)delegate { - RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; +- (void)loadObjectsAtResourcePath:(NSString *)resourcePath delegate:(id)delegate { + RKObjectLoader *loader = [self loaderWithResourcePath:resourcePath]; loader.delegate = delegate; loader.method = RKRequestMethodGET; [loader send]; - - return loader; -} - -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath objectMapping:(RKObjectMapping*)objectMapping delegate:(id)delegate { - RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; - loader.delegate = delegate; - loader.method = RKRequestMethodGET; - loader.objectMapping = objectMapping; - - [loader send]; - - return loader; } ///////////////////////////////////////////////////////////// #pragma mark - Object Instance Loaders -- (RKObjectLoader*)objectLoaderForObject:(id)object method:(RKRequestMethod)method delegate:(id)delegate { - RKObjectLoader *loader = [self loaderForObject:object method:method]; - loader.delegate = delegate; - return loader; -} - -- (RKObjectLoader*)getObject:(id)object delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodGET]; +- (void)getObject:(id)object delegate:(id)delegate { + RKObjectLoader *loader = [self loaderForObject:object method:RKRequestMethodGET]; loader.delegate = delegate; [loader send]; - return loader; } -- (RKObjectLoader*)postObject:(id)object delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodPOST]; +- (void)postObject:(id)object delegate:(id)delegate { + RKObjectLoader *loader = [self loaderForObject:object method:RKRequestMethodPOST]; loader.delegate = delegate; [loader send]; - return loader; } -- (RKObjectLoader*)putObject:(id)object delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodPUT]; +- (void)putObject:(id)object delegate:(id)delegate { + RKObjectLoader *loader = [self loaderForObject:object method:RKRequestMethodPUT]; loader.delegate = delegate; [loader send]; - return loader; } -- (RKObjectLoader*)deleteObject:(id)object delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodDELETE]; +- (void)deleteObject:(id)object delegate:(id)delegate { + RKObjectLoader *loader = [self loaderForObject:object method:RKRequestMethodDELETE]; loader.delegate = delegate; [loader send]; - return loader; } #if NS_BLOCKS_AVAILABLE #pragma mark - Block Configured Object Loaders -- (RKObjectLoader*)loadObjectsAtResourcePath:(NSString*)resourcePath delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { +- (void)loadObjectsAtResourcePath:(NSString*)resourcePath usingBlock:(void(^)(RKObjectLoader *))block { RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; - loader.delegate = delegate; loader.method = RKRequestMethodGET; // Yield to the block for setup block(loader); [loader send]; - - return loader; } -- (RKObjectLoader*)sendObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - RKObjectLoader* loader = [self loaderWithResourcePath:nil]; - loader.delegate = delegate; - loader.sourceObject = object; - loader.targetObject = object; - loader.serializationMIMEType = self.serializationMIMEType; - loader.serializationMapping = [self.mappingProvider serializationMappingForClass:[object class]]; +- (void)sendObject:(id)object toResourcePath:(NSString *)resourcePath usingBlock:(void(^)(RKObjectLoader*))block { + RKObjectLoader *loader = [self loaderForObject:object method:RKRequestMethodInvalid]; + loader.URL = [self.baseURL URLByAppendingResourcePath:resourcePath]; // Yield to the block for setup block(loader); - if (loader.resourcePath == nil) { - loader.resourcePath = [self.router resourcePathForObject:object method:loader.method]; - } - - if (loader.objectMapping == nil) { - if (self.inferMappingsFromObjectTypes) { - RKObjectMapping* objectMapping = [self.mappingProvider objectMappingForClass:[object class]]; - RKLogDebug(@"Auto-selected object mapping %@ for object of type %@", objectMapping, NSStringFromClass([object class])); - loader.objectMapping = objectMapping; - } - } - [loader send]; - return loader; } -- (RKObjectLoader*)sendObject:(id)object method:(RKRequestMethod)method delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - return [self sendObject:object delegate:delegate block:^(RKObjectLoader* loader) { +- (void)sendObject:(id)object method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader*))block { + NSString *resourcePath = [self.router resourcePathForObject:object method:method]; + [self sendObject:object toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) { loader.method = method; block(loader); }]; } -- (RKObjectLoader*)getObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - return [self sendObject:object method:RKRequestMethodGET delegate:delegate block:block]; +- (void)getObject:(id)object usingBlock:(void(^)(RKObjectLoader *))block { + [self sendObject:object method:RKRequestMethodGET usingBlock:block]; } -- (RKObjectLoader*)postObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - return [self sendObject:object method:RKRequestMethodPOST delegate:delegate block:block]; +- (void)postObject:(id)object usingBlock:(void(^)(RKObjectLoader *))block { + [self sendObject:object method:RKRequestMethodPOST usingBlock:block]; } -- (RKObjectLoader*)putObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - return [self sendObject:object method:RKRequestMethodPUT delegate:delegate block:block]; +- (void)putObject:(id)object usingBlock:(void(^)(RKObjectLoader *))block { + [self sendObject:object method:RKRequestMethodPUT usingBlock:block]; } -- (RKObjectLoader*)deleteObject:(id)object delegate:(id)delegate block:(void(^)(RKObjectLoader*))block { - return [self sendObject:object method:RKRequestMethodDELETE delegate:delegate block:block]; +- (void)deleteObject:(id)object usingBlock:(void(^)(RKObjectLoader *))block { + [self sendObject:object method:RKRequestMethodDELETE usingBlock:block]; } #endif // NS_BLOCKS_AVAILABLE #pragma mark - Object Instance Loaders for Non-nested JSON -- (RKObjectLoader*)getObject:(id)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodGET]; - loader.delegate = delegate; - if ([object isMemberOfClass:[objectMapping objectClass]]) { - loader.targetObject = object; - } else { - loader.targetObject = nil; - } - loader.objectMapping = objectMapping; - [loader send]; - return loader; +- (void)getObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate { + [self sendObject:object method:RKRequestMethodGET usingBlock:^(RKObjectLoader *loader) { + loader.delegate = delegate; + loader.objectMapping = objectMapping; + }]; } -- (RKObjectLoader*)postObject:(id)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodPOST]; - loader.delegate = delegate; - if ([object isMemberOfClass:[objectMapping objectClass]]) { - loader.targetObject = object; - } else { - loader.targetObject = nil; - } - loader.objectMapping = objectMapping; - [loader send]; - return loader; +- (void)postObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate { + [self sendObject:object method:RKRequestMethodPOST usingBlock:^(RKObjectLoader *loader) { + loader.delegate = delegate; + loader.objectMapping = objectMapping; + }]; } -- (RKObjectLoader*)putObject:(id)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodPUT]; - loader.delegate = delegate; - if ([object isMemberOfClass:[objectMapping objectClass]]) { - loader.targetObject = object; - } else { - loader.targetObject = nil; - } - loader.objectMapping = objectMapping; - [loader send]; - return loader; +- (void)putObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate { + [self sendObject:object method:RKRequestMethodPUT usingBlock:^(RKObjectLoader *loader) { + loader.delegate = delegate; + loader.objectMapping = objectMapping; + }]; } -- (RKObjectLoader*)deleteObject:(id)object mapResponseWith:(RKObjectMapping*)objectMapping delegate:(id)delegate { - RKObjectLoader* loader = [self loaderForObject:object method:RKRequestMethodDELETE]; - loader.delegate = delegate; - if ([object isMemberOfClass:[objectMapping objectClass]]) { - loader.targetObject = object; - } else { - loader.targetObject = nil; - } - loader.objectMapping = objectMapping; - [loader send]; - return loader; +- (void)deleteObject:(id)object mapResponseWith:(RKObjectMapping *)objectMapping delegate:(id)delegate { + [self sendObject:object method:RKRequestMethodDELETE usingBlock:^(RKObjectLoader *loader) { + loader.delegate = delegate; + loader.objectMapping = objectMapping; + }]; } - (RKRequestCache *)requestCache { @@ -406,12 +331,34 @@ static RKObjectManager* sharedManager = nil; #pragma mark - Deprecations -+ (RKObjectManager*)objectManagerWithBaseURLString:(NSString *)baseURLString { ++ (RKObjectManager *)objectManagerWithBaseURLString:(NSString *)baseURLString { return [self managerWithBaseURLString:baseURLString]; } -+ (RKObjectManager*)objectManagerWithBaseURL:(NSURL *)baseURL { ++ (RKObjectManager *)objectManagerWithBaseURL:(NSURL *)baseURL { return [self managerWithBaseURL:baseURL]; } +- (RKObjectLoader *)objectLoaderWithResourcePath:(NSString *)resourcePath delegate:(id)delegate { + RKObjectLoader* loader = [self loaderWithResourcePath:resourcePath]; + loader.delegate = delegate; + + return loader; +} + +- (RKObjectLoader*)objectLoaderForObject:(id)object method:(RKRequestMethod)method delegate:(id)delegate { + RKObjectLoader *loader = [self loaderForObject:object method:method]; + loader.delegate = delegate; + return loader; +} + +- (void)loadObjectsAtResourcePath:(NSString *)resourcePath objectMapping:(RKObjectMapping *)objectMapping delegate:(id)delegate { + RKObjectLoader *loader = [self loaderWithResourcePath:resourcePath]; + loader.delegate = delegate; + loader.method = RKRequestMethodGET; + loader.objectMapping = objectMapping; + + [loader send]; +} + @end diff --git a/Code/ObjectMapping/RKObjectMapping.h b/Code/ObjectMapping/RKObjectMapping.h index d15dd793..7c07b6bf 100644 --- a/Code/ObjectMapping/RKObjectMapping.h +++ b/Code/ObjectMapping/RKObjectMapping.h @@ -82,9 +82,13 @@ relationship. Relationships are processed using an object mapping as well. @property (nonatomic, readonly) NSArray* mappedKeyPaths; /** - The root keyPath for this object. When the object mapping is being used for serialization - and a root keyPath has been defined, the serialized object will be nested under this root keyPath - before being encoded for transmission to a remote system. + The root key path for the receiver. + + Root key paths are handled differently depending on the context in which the mapping is + being used. If the receiver is used for object mapping, the rootKeyPath specifies a nested + root dictionary that all attribute and relationship mappings will be considered relative to. When + the mapping is used in a serialization context, the rootKeyPath specifies that the serialized content + should be stored in a dictionary nested with the rootKeyPath as the key. @see RKObjectSerializer */ diff --git a/Code/ObjectMapping/RKObjectMappingProvider.h b/Code/ObjectMapping/RKObjectMappingProvider.h index 5211b8f2..419257b9 100644 --- a/Code/ObjectMapping/RKObjectMappingProvider.h +++ b/Code/ObjectMapping/RKObjectMappingProvider.h @@ -22,7 +22,7 @@ #import "RKDynamicObjectMapping.h" // Internal framework contexts -// see RKObjectMappingProvider+Contexts +// @see RKObjectMappingProvider+Contexts.h typedef enum { RKObjectMappingProviderContextObjectsByKeyPath = 1000, RKObjectMappingProviderContextObjectsByType, diff --git a/Specs/Network/RKRequestSpec.m b/Specs/Network/RKRequestSpec.m index 529195c9..f1da28b1 100644 --- a/Specs/Network/RKRequestSpec.m +++ b/Specs/Network/RKRequestSpec.m @@ -735,4 +735,31 @@ NSString *authorization = [request.URLRequest valueForHTTPHeaderField:@"Authorization"]; assertThat(authorization, isNot(nilValue())); } + +- (void)testOnDidLoadResponseBlockInvocation { + RKURL *URL = [RKSpecGetBaseURL() URLByAppendingResourcePath:@"/200"]; + RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader]; + RKRequest *request = [RKRequest requestWithURL:URL]; + __block RKResponse *blockResponse = nil; + request.onDidLoadResponse = ^ (RKResponse *response) { + blockResponse = response; + }; + request.delegate = loader; + [request sendAsynchronously]; + [loader waitForResponse]; + assertThat(blockResponse, is(notNilValue())); +} + +- (void)testOnDidFailLoadWithErrorBlockInvocation { + RKURL *URL = [RKSpecGetBaseURL() URLByAppendingResourcePath:@"/503"]; + RKRequest *request = [RKRequest requestWithURL:URL]; + __block NSError *blockError = nil; + request.onDidFailLoadWithError = ^ (NSError *error) { + blockError = error; + }; + NSError *expectedError = [NSError errorWithDomain:@"Test" code:1234 userInfo:nil]; + [request didFailLoadWithError:expectedError]; + assertThat(blockError, is(notNilValue())); +} + @end diff --git a/Specs/ObjectMapping/RKObjectLoaderSpec.m b/Specs/ObjectMapping/RKObjectLoaderSpec.m index fb841e78..88ca534a 100644 --- a/Specs/ObjectMapping/RKObjectLoaderSpec.m +++ b/Specs/ObjectMapping/RKObjectLoaderSpec.m @@ -50,16 +50,6 @@ @synthesize phone = _phone; @synthesize email = _email; -+ (NSDictionary*)elementToPropertyMappings { - return [NSDictionary dictionaryWithKeysAndObjects: - @"id", @"userID", - @"firstname", @"firstname", - @"lastname", @"lastname", - @"email", @"email", - @"phone", @"phone", - nil]; -} - - (void)willSendWithObjectLoader:(RKObjectLoader *)objectLoader { NSLog(@"RKSpecComplexUser willSendWithObjectLoader: INVOKED!!"); return; @@ -446,7 +436,10 @@ // NOTE: The postObject: should infer the target object from sourceObject and the mapping class RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; - [objectManager postObject:user mapResponseWith:mapping delegate:responseLoader]; + [objectManager postObject:user usingBlock:^(RKObjectLoader *loader) { + loader.delegate = responseLoader; + loader.objectMapping = mapping; + }]; [responseLoader waitForResponse]; assertThatBool([responseLoader success], is(equalToBool(YES))); assertThat(user.email, is(equalTo(@"changed"))); @@ -617,4 +610,125 @@ assertThat(responseLoader.objects, is(empty())); } +#pragma mark - Block Tests + +- (void)testInvocationOfDidLoadObjectBlock { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObject = ^(id object) { + expectedResult = object; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + assertThat(expectedResult, is(notNilValue())); +} + +- (void)testInvocationOfDidLoadObjectBlockIsSingularObjectOfCorrectType { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObject = ^(id object) { + expectedResult = object; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + assertThat(expectedResult, is(instanceOf([RKSpecComplexUser class]))); +} + +- (void)testInvocationOfDidLoadObjectsBlock { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObjects = ^(NSArray *objects) { + expectedResult = objects; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + assertThat(expectedResult, is(notNilValue())); +} + +- (void)testInvocationOfDidLoadObjectsBlocksIsCollectionOfObjects { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObjects = ^(NSArray *objects) { + expectedResult = [objects retain]; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + NSLog(@"The expectedResult = %@", expectedResult); + assertThat(expectedResult, is(instanceOf([NSArray class]))); + assertThat(expectedResult, hasCountOf(1)); + [expectedResult release]; +} + +- (void)testInvocationOfDidLoadObjectsDictionaryBlock { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObjectsDictionary = ^(NSDictionary *dictionary) { + expectedResult = dictionary; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + assertThat(expectedResult, is(notNilValue())); +} + +- (void)testInvocationOfDidLoadObjectsDictionaryBlocksIsDictionaryOfObjects { + RKObjectManager* objectManager = [RKObjectManager managerWithBaseURL:RKSpecGetBaseURL()]; + RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; + RKObjectLoader* objectLoader = [objectManager loaderWithResourcePath:@"/JSON/ComplexNestedUser.json"]; + objectLoader.delegate = responseLoader; + objectLoader.method = RKRequestMethodGET; + objectLoader.mappingProvider = [self providerForComplexUser]; + __block id expectedResult = nil; + objectLoader.onDidLoadObjectsDictionary = ^(NSDictionary *dictionary) { + expectedResult = dictionary; + }; + + [objectLoader sendAsynchronously]; + [responseLoader waitForResponse]; + assertThat(expectedResult, is(instanceOf([NSDictionary class]))); + assertThat(expectedResult, hasCountOf(1)); +} + +// NOTE: Errors are fired in a number of contexts within the RKObjectLoader. We have centralized the cases into a private +// method and test that one case here. There should be better coverage for this. +- (void)testInvocationOfOnDidFailWithError { + RKObjectLoader *loader = [RKObjectLoader loaderWithURL:nil mappingProvider:nil]; + NSError *expectedError = [NSError errorWithDomain:@"Testing" code:1234 userInfo:nil]; + __block NSError *blockError = nil; + loader.onDidFailWithError = ^(NSError *error) { + blockError = error; + }; + [loader performSelector:@selector(informDelegateOfError:) withObject:expectedError]; + assertThat(blockError, is(equalTo(expectedError))); +} + @end diff --git a/Specs/ObjectMapping/RKObjectManagerSpec.m b/Specs/ObjectMapping/RKObjectManagerSpec.m index 92422d3d..815b2330 100644 --- a/Specs/ObjectMapping/RKObjectManagerSpec.m +++ b/Specs/ObjectMapping/RKObjectManagerSpec.m @@ -218,10 +218,14 @@ RKObjectMapperSpecModel* human = [[RKObjectMapperSpecModel new] autorelease]; human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; - RKObjectLoader* loader = [objectManager getObject:human delegate:responseLoader]; + __block RKObjectLoader *objectLoader = nil; + [objectManager getObject:human usingBlock:^(RKObjectLoader *loader) { + loader.delegate = responseLoader; + objectLoader = loader; + }]; [responseLoader waitForResponse]; - RKLogCritical(@"%@", [loader.URLRequest allHTTPHeaderFields]); - assertThat([loader.URLRequest valueForHTTPHeaderField:@"Content-Length"], is(equalTo(@"0"))); + RKLogCritical(@"%@", [objectLoader.URLRequest allHTTPHeaderFields]); + assertThat([objectLoader.URLRequest valueForHTTPHeaderField:@"Content-Length"], is(equalTo(@"0"))); } - (void)testShouldNotSetAContentBodyOnADELETE { @@ -236,10 +240,14 @@ RKObjectMapperSpecModel* human = [[RKObjectMapperSpecModel new] autorelease]; human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; - RKObjectLoader* loader = [objectManager deleteObject:human delegate:responseLoader]; + __block RKObjectLoader *objectLoader = nil; + [objectManager deleteObject:human usingBlock:^(RKObjectLoader *loader) { + loader.delegate = responseLoader; + objectLoader = loader; + }]; [responseLoader waitForResponse]; - RKLogCritical(@"%@", [loader.URLRequest allHTTPHeaderFields]); - assertThat([loader.URLRequest valueForHTTPHeaderField:@"Content-Length"], is(equalTo(@"0"))); + RKLogCritical(@"%@", [objectLoader.URLRequest allHTTPHeaderFields]); + assertThat([objectLoader.URLRequest valueForHTTPHeaderField:@"Content-Length"], is(equalTo(@"0"))); } #pragma mark - Block Helpers @@ -251,7 +259,8 @@ [objectManager.mappingProvider registerMapping:mapping withRootKeyPath:@"human"]; RKSpecResponseLoader* responseLoader = [RKSpecResponseLoader responseLoader]; - [objectManager loadObjectsAtResourcePath:@"/JSON/humans/1.json" delegate:responseLoader block:^(RKObjectLoader* loader) { + [objectManager loadObjectsAtResourcePath:@"/JSON/humans/1.json" usingBlock:^(RKObjectLoader* loader) { + loader.delegate = responseLoader; loader.objectMapping = mapping; }]; [responseLoader waitForResponse]; @@ -270,7 +279,8 @@ RKObjectMapperSpecModel* human = [[RKObjectMapperSpecModel new] autorelease]; human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; - [objectManager deleteObject:human delegate:responseLoader block:^(RKObjectLoader* loader) { + [objectManager deleteObject:human usingBlock:^(RKObjectLoader* loader) { + loader.delegate = responseLoader; loader.resourcePath = @"/humans/1"; }]; [responseLoader waitForResponse]; @@ -287,7 +297,9 @@ RKObjectMapperSpecModel* human = [[RKObjectMapperSpecModel new] autorelease]; human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; - [objectManager deleteObject:human delegate:responseLoader block:^(RKObjectLoader* loader) { + [objectManager sendObject:human toResourcePath:@"/humans/1" usingBlock:^(RKObjectLoader *loader) { + loader.method = RKRequestMethodDELETE; + loader.delegate = responseLoader; loader.resourcePath = @"/humans/1"; }]; [responseLoader waitForResponse]; @@ -304,8 +316,9 @@ RKObjectMapperSpecModel* human = [[RKObjectMapperSpecModel new] autorelease]; human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; - [objectManager deleteObject:human delegate:responseLoader block:^(RKObjectLoader* loader) { - loader.resourcePath = @"/humans/1"; + [objectManager sendObject:human toResourcePath:@"/humans/1" usingBlock:^(RKObjectLoader *loader) { + loader.method = RKRequestMethodDELETE; + loader.delegate = responseLoader; loader.objectMapping = mapping; }]; [responseLoader waitForResponse]; @@ -324,14 +337,16 @@ human.name = @"Blake Watters"; human.age = [NSNumber numberWithInt:28]; NSDictionary *myParams = [NSDictionary dictionaryWithObject:@"bar" forKey:@"foo"]; - RKObjectLoader* loader = [objectManager sendObject:human delegate:responseLoader block:^(RKObjectLoader* loader) { + __block RKObjectLoader* objectLoader = nil; + [objectManager sendObject:human toResourcePath:@"/humans/1" usingBlock:^(RKObjectLoader *loader) { + loader.delegate = responseLoader; loader.method = RKRequestMethodPOST; - loader.resourcePath = @"/humans/1"; loader.objectMapping = mapping; loader.params = myParams; + objectLoader = loader; }]; [responseLoader waitForResponse]; - assertThat(loader.params, is(equalTo(myParams))); + assertThat(objectLoader.params, is(equalTo(myParams))); } @end diff --git a/Specs/Server/server.rb b/Specs/Server/server.rb index afd9ca30..3a7c77d2 100644 --- a/Specs/Server/server.rb +++ b/Specs/Server/server.rb @@ -102,6 +102,11 @@ class RestKit::SpecServer < Sinatra::Base "File Not Found" end + get '/503' do + status 503 + "Internal Server Error" + end + get '/encoding' do status 200 content_type 'text/plain; charset=us-ascii'