From a9ef1fe39dc92e7e8e4bd75039fcd3e54b413b10 Mon Sep 17 00:00:00 2001 From: Blake Watters Date: Thu, 7 Mar 2013 19:25:01 -0500 Subject: [PATCH] Introduce a new heuristic based approach for determining if a response can skip the mapping process. Also introduces a new Network + CoreData logging component and reduces the chattiness of the debug logging level for Core Data Network events. --- .../Network/RKManagedObjectRequestOperation.m | 33 +++++++++++++++---- Code/ObjectMapping/RKHTTPUtilities.h | 7 ++++ Code/ObjectMapping/RKHTTPUtilities.m | 14 ++++++++ Code/Support/lcl_config_components_RK.h | 1 + 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Code/Network/RKManagedObjectRequestOperation.m b/Code/Network/RKManagedObjectRequestOperation.m index 89f35d47..c759df0a 100644 --- a/Code/Network/RKManagedObjectRequestOperation.m +++ b/Code/Network/RKManagedObjectRequestOperation.m @@ -35,13 +35,12 @@ // Set Logging Component #undef RKLogComponent -#define RKLogComponent RKlcl_cRestKitCoreData +#define RKLogComponent RKlcl_cRestKitNetworkCoreData @interface RKEntityMappingEvent : NSObject @property (nonatomic, copy) id rootKey; @property (nonatomic, copy) NSString *keyPath; @property (nonatomic, strong) RKEntityMapping *entityMapping; -@property (nonatomic, readonly) BOOL canSkipMapping; + (instancetype)eventWithRootKey:(id)rootKey keyPath:(NSString *)keyPath entityMapping:(RKEntityMapping *)entityMapping; @end @@ -299,6 +298,8 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re @property (nonatomic, strong, readwrite) RKMappingResult *mappingResult; @property (nonatomic, copy) id (^willMapDeserializedResponseBlock)(id deserializedResponseBody); @property (nonatomic, strong) NSArray *entityMappingEvents; + +@property (nonatomic, strong) NSCachedURLResponse *cachedResponse; @end @implementation RKManagedObjectRequestOperation @@ -312,6 +313,7 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re if (self) { self.savesToPersistentStore = YES; self.deletesOrphanedObjects = YES; + self.cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:requestOperation.request]; } return self; } @@ -372,14 +374,31 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re // RKResponseHasBeenMappedCacheUserInfoKey is stored by RKObjectRequestOperation - (BOOL)canSkipMapping { - NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.HTTPRequestOperation.request]; + // Is the request cacheable + if (!self.cachedResponse) return NO; + NSURLRequest *request = self.HTTPRequestOperation.request; + if (! [[request HTTPMethod] isEqualToString:@"GET"] && ! [[request HTTPMethod] isEqualToString:@"HEAD"]) return NO; + NSHTTPURLResponse *response = (NSHTTPURLResponse *)self.HTTPRequestOperation.response; + if (! [RKCacheableStatusCodes() containsIndex:response.statusCode]) return NO; + + // Check for a change in the Etag + NSString *cachedEtag = [[(NSHTTPURLResponse *)[self.cachedResponse response] allHeaderFields] objectForKey:@"Etag"]; + NSString *responseEtag = [[response allHeaderFields] objectForKey:@"Etag"]; + if (! [cachedEtag isEqualToString:responseEtag]) return NO; + + // Response data has changed + NSData *responseData = self.HTTPRequestOperation.responseData; + if (! [responseData isEqualToData:[self.cachedResponse data]]) return NO; + + // Check that we have mapped this response previously + NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request]; return [[cachedResponse.userInfo objectForKey:RKResponseHasBeenMappedCacheUserInfoKey] boolValue]; } - (RKMappingResult *)performMappingOnResponse:(NSError **)error { - if (self.canSkipMapping) { - RKLogDebug(@"Managed object mapping requested for cached response: skipping mapping..."); + if ([self canSkipMapping]) { + RKLogDebug(@"Managed object mapping requested for cached response which was previously mapped: skipping..."); NSURL *URL = RKRelativeURLFromURLAndResponseDescriptors(self.HTTPRequestOperation.response.URL, self.responseDescriptors); NSArray *fetchRequests = RKArrayOfFetchRequestFromBlocksWithURL(self.fetchRequestBlocks, URL); NSMutableArray *managedObjects = [NSMutableArray array]; @@ -470,7 +489,7 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re RKLogTrace(@"Fetched local objects matching URL '%@' with fetch request '%@': %@", URL, fetchRequest, _blockObjects); [localObjects addObjectsFromArray:_blockObjects]; } else { - RKLogDebug(@"Fetch request block %@ returned nil fetch request for URL: '%@'", fetchRequestBlock, URL); + RKLogTrace(@"Fetch request block %@ returned nil fetch request for URL: '%@'", fetchRequestBlock, URL); } } @@ -489,7 +508,7 @@ static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *re return YES; } - if (self.canSkipMapping) { + if ([self canSkipMapping]) { RKLogDebug(@"Skipping deletion of orphaned objects: 304 (Not Modified) status code encountered"); return YES; } diff --git a/Code/ObjectMapping/RKHTTPUtilities.h b/Code/ObjectMapping/RKHTTPUtilities.h index c1539522..ffb97636 100644 --- a/Code/ObjectMapping/RKHTTPUtilities.h +++ b/Code/ObjectMapping/RKHTTPUtilities.h @@ -78,6 +78,13 @@ NSRange RKStatusCodeRangeForClass(RKStatusCodeClass statusCodeClass); */ NSIndexSet *RKStatusCodeIndexSetForClass(RKStatusCodeClass statusCodeClass); +/** + Creates and returns a new index set including all HTTP response status codes that are cacheable. + + @return A new index set containing all cacheable status codes. + */ +NSIndexSet *RKCacheableStatusCodes(); + /** Returns string representation of a given HTTP status code. diff --git a/Code/ObjectMapping/RKHTTPUtilities.m b/Code/ObjectMapping/RKHTTPUtilities.m index 0a20fbb9..970ef414 100644 --- a/Code/ObjectMapping/RKHTTPUtilities.m +++ b/Code/ObjectMapping/RKHTTPUtilities.m @@ -32,6 +32,20 @@ NSIndexSet *RKStatusCodeIndexSetForClass(RKStatusCodeClass statusCodeClass) return [NSIndexSet indexSetWithIndexesInRange:RKStatusCodeRangeForClass(statusCodeClass)]; } +NSIndexSet *RKCacheableStatusCodes() +{ + NSMutableIndexSet *cacheableStatusCodes = [NSMutableIndexSet indexSet]; + [cacheableStatusCodes addIndex:200]; + [cacheableStatusCodes addIndex:304]; + [cacheableStatusCodes addIndex:203]; + [cacheableStatusCodes addIndex:300]; + [cacheableStatusCodes addIndex:301]; + [cacheableStatusCodes addIndex:302]; + [cacheableStatusCodes addIndex:307]; + [cacheableStatusCodes addIndex:410]; + return cacheableStatusCodes; +} + NSString *RKStringFromRequestMethod(RKRequestMethod method) { switch (method) { diff --git a/Code/Support/lcl_config_components_RK.h b/Code/Support/lcl_config_components_RK.h index b364eb31..36382920 100644 --- a/Code/Support/lcl_config_components_RK.h +++ b/Code/Support/lcl_config_components_RK.h @@ -54,6 +54,7 @@ _RKlcl_component(RestKit, "restkit", _RKlcl_component(RestKitCoreData, "restkit.core_data", "RestKit/CoreData") _RKlcl_component(RestKitCoreDataCache, "restkit.core_data.cache", "RestKit/CoreData/Cache") _RKlcl_component(RestKitNetwork, "restkit.network", "RestKit/Network") +_RKlcl_component(RestKitNetworkCoreData, "restkit.network.core_data", "RestKit/Network/CoreData") _RKlcl_component(RestKitObjectMapping, "restkit.object_mapping", "RestKit/ObjectMapping") _RKlcl_component(RestKitSearch, "restkit.search", "RestKit/Search") _RKlcl_component(RestKitSupport, "restkit.support", "RestKit/Support")