Add support for the registration of HTTP request operation subclasses on the manager to support easy customization of request operation handling. refs #997

This commit is contained in:
Blake Watters
2012-10-16 11:25:00 -04:00
parent d6c429e735
commit f4bfdb389b
7 changed files with 103 additions and 57 deletions

View File

@@ -54,8 +54,7 @@
@property (nonatomic, strong) NSSet *acceptableContentTypes;
/**
Whether the response received a 304 response, whether via the initial request, or by virtue of
cache revalidation occurring from NSURLCache.
Whether the response received a 304 response, whether via the initial request, or by virtue of cache revalidation occurring from `NSURLCache`.
*/
@property (nonatomic, readonly) BOOL wasNotModified;

View File

@@ -92,8 +92,8 @@
return [[RKMappingResult alloc] initWithDictionary:@{}];
}
RKManagedObjectResponseMapperOperation *mapperOperation = [[RKManagedObjectResponseMapperOperation alloc] initWithResponse:self.response
data:self.responseData
RKManagedObjectResponseMapperOperation *mapperOperation = [[RKManagedObjectResponseMapperOperation alloc] initWithResponse:self.HTTPRequestOperation.response
data:self.HTTPRequestOperation.responseData
responseDescriptors:self.responseDescriptors];
mapperOperation.targetObjectID = self.targetObjectID;
mapperOperation.managedObjectContext = self.privateContext;
@@ -113,8 +113,8 @@
__block BOOL _blockSuccess = YES;
if (self.targetObjectID
&& NSLocationInRange(self.response.statusCode, RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful))
&& [[[self.request HTTPMethod] uppercaseString] isEqualToString:@"DELETE"]) {
&& NSLocationInRange(self.HTTPRequestOperation.response.statusCode, RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful))
&& [[[self.HTTPRequestOperation.request HTTPMethod] uppercaseString] isEqualToString:@"DELETE"]) {
// 2xx DELETE request, proceed with deletion from the MOC
__block NSError *_blockError = nil;
@@ -142,7 +142,7 @@
__block NSArray *_blockObjects;
// Pass the fetch request blocks a relative `NSURL` object if possible
NSURL *URL = [self.request URL];
NSURL *URL = [self.HTTPRequestOperation.request URL];
NSArray *baseURLs = [self.responseDescriptors valueForKeyPath:@"@distinctUnionOfObjects.baseURL"];
if ([baseURLs count] == 1) {
NSURL *baseURL = baseURLs[0];
@@ -180,7 +180,7 @@
return YES;
}
if (! [[self.request.HTTPMethod uppercaseString] isEqualToString:@"GET"]) {
if (! [[self.HTTPRequestOperation.request.HTTPMethod uppercaseString] isEqualToString:@"GET"]) {
RKLogDebug(@"Skipping cleanup of objects via managed object cache: only used for GET requests.");
return YES;
}

View File

@@ -390,6 +390,18 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
/// @name Creating Object Request Operations
///-----------------------------------------
/**
Sets the `RKHTTPRequestOperation` subclass to be used when constructing HTTP request operations for requests dispatched through the manager.
When set, an instance of the given class will be initialized via `initWithRequest:` each time that the receiver constructs an HTTP request operation. HTTP request operations are used to initialize instances of `RKObjectRequestOperation` and are responsible for managing the HTTP request/response lifecycle of a request whose response is destined to be object mapped. Providing a subclass implementation of `RKHTTPRequestOperation` allows the behavior of all requests sent through the manager to be changed.
@param operationClass A class object inheriting from `RKHTTPRequestOperation` to be used for HTTP requests dispatched through the manager.
@raises `NSInvalidArgumentException` Raised if the given class does not inherit from `RKHTTPRequestOperation`.
@see `RKHTTPRequestOperation`
@warning The given class must inherit from `RKHTTPRequestOperation`, else an exception will be raised.
*/
- (void)setHTTPOperationClass:(Class)operationClass;
/**
Creates an `RKObjectRequestOperation` operation with the given request and sets the completion block with the given success and failure blocks.

View File

@@ -121,6 +121,7 @@ static NSString *RKMIMETypeFromAFHTTPClientParameterEncoding(AFHTTPClientParamet
@property (nonatomic, strong) NSMutableArray *mutableResponseDescriptors;
@property (nonatomic, strong) NSMutableArray *mutableFetchRequestBlocks;
@property (nonatomic, strong) NSString *acceptHeaderValue;
@property (nonatomic) Class HTTPOperationClass;
@end
@implementation RKObjectManager
@@ -288,11 +289,23 @@ static NSString *RKMIMETypeFromAFHTTPClientParameterEncoding(AFHTTPClientParamet
return [self.HTTPClient multipartFormRequestWithMethod:stringMethod path:requestPath parameters:requestParameters constructingBodyWithBlock:block];
}
- (void)setHTTPOperationClass:(Class)operationClass
{
NSAssert(operationClass == nil || [operationClass isSubclassOfClass:[RKHTTPRequestOperation class]], @"");
_HTTPOperationClass = operationClass;
}
- (RKHTTPRequestOperation *)HTTPOperationWithRequest:(NSURLRequest *)request
{
Class operationClass = self.HTTPOperationClass ?: [RKHTTPRequestOperation class];
return [[operationClass alloc] initWithRequest:request];
}
- (RKObjectRequestOperation *)objectRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure
{
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.responseDescriptors];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithHTTPRequestOperation:[self HTTPOperationWithRequest:request] responseDescriptors:self.responseDescriptors];
[operation setCompletionBlockWithSuccess:success failure:failure];
return operation;
}
@@ -302,7 +315,7 @@ static NSString *RKMIMETypeFromAFHTTPClientParameterEncoding(AFHTTPClientParamet
success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure
{
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:self.responseDescriptors];
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithHTTPRequestOperation:[self HTTPOperationWithRequest:request] responseDescriptors:self.responseDescriptors];
[operation setCompletionBlockWithSuccess:success failure:failure];
operation.managedObjectContext = managedObjectContext;
operation.managedObjectCache = self.managedObjectStore.managedObjectCache;
@@ -514,7 +527,7 @@ static NSString *RKMIMETypeFromAFHTTPClientParameterEncoding(AFHTTPClientParamet
continue;
}
NSURLRequest *request = [(RKObjectRequestOperation *)operation request];
NSURLRequest *request = [(RKObjectRequestOperation *)operation HTTPRequestOperation].request;
NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL([request URL], self.baseURL);
if ((!methodName || [methodName isEqualToString:[request HTTPMethod]]) && [pathMatcher matchesPath:pathAndQueryString tokenizeQueryStrings:NO parsedArguments:nil]) {
[operation cancel];

View File

@@ -56,10 +56,24 @@
///-----------------------------------------------
/**
Initializes an object request operation with a request object and a set of response descriptors.
Initializes an object request operation with an HTTP request operation and a set of response descriptors.
This is the designated initializer.
@param request The request object to be used with the underlying network operation.
@param responseDescriptors An array of `RKResponseDescriptor` objects specifying how object mapping is to be performed on the response loaded by the network operation.
@return The receiver, initialized with the given request and response descriptors.
*/
- (id)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOperation responseDescriptors:(NSArray *)responseDescriptors;
/**
Initializes an object request operation with a request object and a set of response descriptors.
This method is a convenience initializer for initializing an object request operation from a URL request with the default HTTP operation class `RKHTTPRequestOperation`. This method is functionally equivalent to the following example code:
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithHTTPRequestOperation:requestOperation responseDescriptors:responseDescriptors];
@param request The request object to be used with the underlying network operation.
@param responseDescriptors An array of `RKResponseDescriptor` objects specifying how object mapping is to be performed on the response loaded by the network operation.
@return The receiver, initialized with the given request and response descriptors.
@@ -100,26 +114,14 @@
*/
@property (nonatomic, strong, readonly) NSError *error;
///-----------------------------------
/// @name Accessing Network Properties
///-----------------------------------
///-------------------------------------------
/// @name Accessing the HTTP Request Operation
///-------------------------------------------
/**
The request object used by the underlying `RKHTTPRequestOperation` network operation.
The underlying `RKHTTPRequestOperation` object used to manage the HTTP request/response lifecycle of the object request operation.
*/
@property (nonatomic, strong, readonly) NSURLRequest *request;
/**
The response object loaded by the underlying `RKHTTPRequestOperation` network operation.
*/
@property (nonatomic, readonly) NSHTTPURLResponse *response;
/**
The response data loaded by the underlying `RKHTTRequestOperation` network operation.
Object mapping is performed on the deserialized `responseData`.
*/
@property (nonatomic, readonly) NSData *responseData;
@property (nonatomic, strong, readonly) RKHTTPRequestOperation *HTTPRequestOperation;
///-------------------------------------------------------
/// @name Setting the Completion Block and Callback Queues

View File

@@ -45,11 +45,10 @@ static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes()
}
@interface RKObjectRequestOperation ()
@property (nonatomic, strong, readwrite) RKHTTPRequestOperation *requestOperation;
@property (nonatomic, strong, readwrite) RKHTTPRequestOperation *HTTPRequestOperation;
@property (nonatomic, strong, readwrite) NSArray *responseDescriptors;
@property (nonatomic, strong, readwrite) RKMappingResult *mappingResult;
@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong, readwrite) NSURLRequest *request;
@end
@implementation RKObjectRequestOperation
@@ -62,23 +61,30 @@ static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes()
#endif
}
- (id)initWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors
// Designated initializer
- (id)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOperation responseDescriptors:(NSArray *)responseDescriptors
{
NSParameterAssert(request);
NSParameterAssert(requestOperation);
NSParameterAssert(responseDescriptors);
self = [self init];
if (self) {
self.request = request;
self.responseDescriptors = responseDescriptors;
self.requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
self.requestOperation.acceptableContentTypes = [RKMIMETypeSerialization registeredMIMETypes];
self.requestOperation.acceptableStatusCodes = RKObjectRequestOperationAcceptableMIMETypes();
self.HTTPRequestOperation = requestOperation;
self.HTTPRequestOperation.acceptableContentTypes = [RKMIMETypeSerialization registeredMIMETypes];
self.HTTPRequestOperation.acceptableStatusCodes = RKObjectRequestOperationAcceptableMIMETypes();
}
return self;
}
- (id)initWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors
{
NSParameterAssert(request);
NSParameterAssert(responseDescriptors);
return [self initWithHTTPRequestOperation:[[RKHTTPRequestOperation alloc] initWithRequest:request] responseDescriptors:responseDescriptors];
}
- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue
{
if (successCallbackQueue != _successCallbackQueue) {
@@ -142,21 +148,11 @@ static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes()
};
}
- (NSHTTPURLResponse *)response
{
return (NSHTTPURLResponse *)self.requestOperation.response;
}
- (NSData *)responseData
{
return self.requestOperation.responseData;
}
- (RKMappingResult *)performMappingOnResponse:(NSError **)error
{
// Spin up an RKObjectResponseMapperOperation
RKObjectResponseMapperOperation *mapperOperation = [[RKObjectResponseMapperOperation alloc] initWithResponse:self.response
data:self.responseData
RKObjectResponseMapperOperation *mapperOperation = [[RKObjectResponseMapperOperation alloc] initWithResponse:self.HTTPRequestOperation.response
data:self.HTTPRequestOperation.responseData
responseDescriptors:self.responseDescriptors];
mapperOperation.targetObject = self.targetObject;
[mapperOperation start];
@@ -176,7 +172,7 @@ static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes()
- (void)cancel
{
[super cancel];
[self.requestOperation cancel];
[self.HTTPRequestOperation cancel];
}
- (void)main
@@ -184,12 +180,12 @@ static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes()
if (self.isCancelled) return;
// Send the request
[self.requestOperation start];
[self.requestOperation waitUntilFinished];
[self.HTTPRequestOperation start];
[self.HTTPRequestOperation waitUntilFinished];
if (self.requestOperation.error) {
RKLogError(@"Object request failed: Underlying HTTP request operation failed with error: %@", self.requestOperation.error);
self.error = self.requestOperation.error;
if (self.HTTPRequestOperation.error) {
RKLogError(@"Object request failed: Underlying HTTP request operation failed with error: %@", self.HTTPRequestOperation.error);
self.error = self.HTTPRequestOperation.error;
return;
}

View File

@@ -42,6 +42,11 @@
@end
@interface RKTestHTTPRequestOperation : RKHTTPRequestOperation
@end
@implementation RKTestHTTPRequestOperation : RKHTTPRequestOperation
@end
@interface RKObjectManagerTest : RKTestCase
@property (nonatomic, strong) RKObjectManager *objectManager;
@@ -354,7 +359,7 @@
temporaryHuman.name = @"My Name";
temporaryHuman.railsID = @204;
RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodGET path:nil parameters:@{@"this": @"that"}];
expect([operation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
expect([operation.HTTPRequestOperation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
}
- (void)testThatObjectParametersAreNotSentDuringDeleteObject
@@ -363,7 +368,7 @@
temporaryHuman.name = @"My Name";
temporaryHuman.railsID = @204;
RKManagedObjectRequestOperation *operation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodDELETE path:nil parameters:@{@"this": @"that"}];
expect([operation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
expect([operation.HTTPRequestOperation.request.URL absoluteString]).to.equal(@"http://127.0.0.1:4567/humans/204?this=that");
}
- (void)testInitializationOfObjectRequestOperationProducesCorrectURLRequest
@@ -404,6 +409,25 @@
expect([request allHTTPHeaderFields][@"Accept"]).to.equal(@"application/json");
}
- (void)testRegistrationOfHTTPRequestOperationClass
{
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
[manager setHTTPOperationClass:[RKTestHTTPRequestOperation class]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/test" relativeToURL:manager.baseURL]];
RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
expect(operation.HTTPRequestOperation).to.beKindOf([RKTestHTTPRequestOperation class]);
}
- (void)testSettingNilHTTPRequestOperationClassRestoresDefaultHTTPOperationClass
{
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
[manager setHTTPOperationClass:[RKTestHTTPRequestOperation class]];
[manager setHTTPOperationClass:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/test" relativeToURL:manager.baseURL]];
RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request success:nil failure:nil];
expect(operation.HTTPRequestOperation).to.beKindOf([RKHTTPRequestOperation class]);
}
// TODO: Move to Core Data specific spec file...
//- (void)testShouldLoadAHuman
//{