Move management of RKResponseHasBeenMapped to RKObjectRequestOperation instead of RKManagedObjectRequestOperation. Add unit tests.

This commit is contained in:
Blake Watters
2013-03-07 14:39:31 -05:00
parent 248d1d3779
commit 318f9659f6
6 changed files with 69 additions and 21 deletions

View File

@@ -369,10 +369,11 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re
[self.responseMapperOperation cancel];
}
// RKResponseHasBeenMappedCacheUserInfoKey is stored by RKObjectRequestOperation
- (BOOL)canSkipMapping
{
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.HTTPRequestOperation.request];
return [[cachedResponse.userInfo objectForKey:@"RKResponseHasBeenMapped"] boolValue];
return [[cachedResponse.userInfo objectForKey:RKResponseHasBeenMappedCacheUserInfoKey] boolValue];
}
- (RKMappingResult *)performMappingOnResponse:(NSError **)error
@@ -635,17 +636,7 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re
if (! success || [self isCancelled]) {
self.error = error;
return;
}
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.HTTPRequestOperation.request];
if (cachedResponse) {
// We're all done mapping this request. Now we set a flag on the cache entry's userInfo dictionary to indicate that the request
// corresponding to the cache entry completed successfully, and we can reliably skip mapping if a subsequent request results
// in the use of this cachedResponse.
NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionaryWithObject:@YES forKey:@"RKResponseHasBeenMapped"];
NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];
[[NSURLCache sharedURLCache] storeCachedResponse:newCachedResponse forRequest:self.HTTPRequestOperation.request];
}
}
// Refetch all managed objects nested at key paths within the results dictionary before returning
if (self.mappingResult) {

View File

@@ -22,6 +22,11 @@
#import "RKMappingResult.h"
#import "RKMapperOperation.h"
/**
The key for a Boolean NSNumber value that indicates if a `NSCachedURLResponse` stored in the `NSURLCache` has been object mapped to completion. This key is stored on the `userInfo` of the cached response, if any, just before an `RKObjectRequestOperation` transitions to the finished state.
*/
extern NSString * const RKResponseHasBeenMappedCacheUserInfoKey;
/**
`RKObjectRequestOperation` is an `NSOperation` subclass that implements object mapping on the response body of an `NSHTTPResponse` loaded via an `RKHTTPRequestOperation`.

View File

@@ -38,6 +38,7 @@
NSString * const RKObjectRequestOperationDidStartNotification = @"RKObjectRequestOperationDidStartNotification";
NSString * const RKObjectRequestOperationDidFinishNotification = @"RKObjectRequestOperationDidFinishNotification";
NSString * const RKResponseHasBeenMappedCacheUserInfoKey = @"RKResponseHasBeenMapped";
static void RKIncrementNetworkActivityIndicator()
{
@@ -315,8 +316,21 @@ static NSString *RKStringDescribingURLResponseWithData(NSURLResponse *response,
}
self.mappingResult = mappingResult;
[self willFinish];
if (self.error) self.mappingResult = nil;
if (self.error) {
self.mappingResult = nil;
} else {
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.HTTPRequestOperation.request];
if (cachedResponse) {
// We're all done mapping this request. Now we set a flag on the cache entry's userInfo dictionary to indicate that the request
// corresponding to the cache entry completed successfully, and we can reliably skip mapping if a subsequent request results
// in the use of this cachedResponse.
NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary];
[userInfo setObject:@YES forKey:RKResponseHasBeenMappedCacheUserInfoKey];
NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];
[[NSURLCache sharedURLCache] storeCachedResponse:newCachedResponse forRequest:self.HTTPRequestOperation.request];
}
}
}
- (void)main

View File

@@ -148,7 +148,7 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
}
// 304 'Not Modified'
- (void)testThatManagedObjectsAreFetchedWhenHandlingANotModifiedResponse
- (void)testThatManagedObjectsAreFetchedWhenHandlingAResponseThatCanSkipMapping
{
RKFetchRequestBlock fetchRequestBlock = ^(NSURL *URL){
return [NSFetchRequest fetchRequestWithEntityName:@"Human"];
@@ -156,7 +156,15 @@ NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"Human" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/204_with_not_modified_status" relativeToURL:[RKTestFactory baseURL]]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/204" relativeToURL:[RKTestFactory baseURL]]];
// Store a cache entry indicating that the response has been previously mapped
NSData *responseData = [@"{}" dataUsingEncoding:NSUTF8StringEncoding];
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:[request URL] statusCode:200 HTTPVersion:@"1.1" headerFields:nil];
NSAssert(response, @"Failed to build cached response");
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:responseData userInfo:@{RKResponseHasBeenMappedCacheUserInfoKey: @YES} storagePolicy:NSURLCacheStorageAllowed];
[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:request];
RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"Human" inManagedObjectStore:managedObjectStore];
[humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];

View File

@@ -112,6 +112,7 @@
NSArray *validErrorCodes = @[ @(NSURLErrorCannotDecodeContentData), @(NSURLErrorCannotFindHost) ];
assertThat(validErrorCodes, hasItem(@([requestOperation.error code])));
}
- (void)testSendingAnObjectRequestOperationToAnBrokenURL
{
NSMutableURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://invalid••™¡.is"]];
@@ -752,4 +753,38 @@
expect(user.phone).to.equal(@"867-5309");
}
#pragma mark -
- (void)testThatCacheEntryIsFlaggedWhenMappingCompletes
{
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestComplexUser class]];
[userMapping addAttributeMappingsFromDictionary:@{ @"name": @"email" }];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:nil keyPath:@"human" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/coredata/etag" relativeToURL:[RKTestFactory baseURL]]];
RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
[requestOperation start];
expect(requestOperation.error).to.beNil();
NSCachedURLResponse *response = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
expect(response).notTo.beNil();
expect([response.userInfo valueForKey:RKResponseHasBeenMappedCacheUserInfoKey]).to.beTruthy();
}
- (void)testThatCacheEntryIsNotFlaggedWhenMappingFails
{
RKObjectMapping *userMapping = [RKObjectMapping mappingForClass:[RKTestComplexUser class]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:userMapping pathPattern:@"/mismatch" keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/coredata/etag" relativeToURL:[RKTestFactory baseURL]]];
RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[ responseDescriptor ]];
[requestOperation start];
expect(requestOperation.error).notTo.beNil();
expect([requestOperation.error localizedDescription]).to.equal(@"No response descriptors match the response loaded.");
NSCachedURLResponse *response = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
expect(response).notTo.beNil();
expect([response.userInfo valueForKey:RKResponseHasBeenMappedCacheUserInfoKey]).to.beFalsy();
}
@end

View File

@@ -287,11 +287,6 @@ class RestKitTestServer < Sinatra::Base
status 304
end
get '/204_with_not_modified_status' do
status 204
response.headers['Status'] = '304 Not Modified'
end
delete '/humans/1234/whitespace' do
content_type 'application/json'
status 200