mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-26 13:55:40 +08:00
Add support for returning fetched objects when a 304 'Not Modified' response is loaded. closes #1006
This commit is contained in:
@@ -88,6 +88,10 @@
|
||||
## Managed Object Context Save Behaviors
|
||||
|
||||
The results of the operation can either be 'pushed' to the parent context or saved to the persistent store. Configuration is available via the `savesToPersistentStore` property. If an error is encountered while saving the managed object context, then the operation is considered to have failed and the `error` property will be set to the `NSError` object returned by the failed save.
|
||||
|
||||
## 304 'Not Modified' Responses
|
||||
|
||||
In the event that a managed object request operation loads a 304 'Not Modified' response for an HTTP request no object mapping is performed as Core Data is assumed to contain a managed object representation of the resource requested. No object mapping is performed on the cached response body, making a cache hit for a managed object request operation a very lightweight operation. To build the mapping result returned to the caller, all of the fetch request blocks matching the request URL will be invoked and each fetch request returned is executed against the managed object context and the objects returned are added to the mapping result. Please note that all managed objects returned in the mapping result for a 'Not Modified' response will be returned under the `[NSNull null]` key path.
|
||||
|
||||
## Limitations and Caveats
|
||||
|
||||
@@ -165,10 +169,10 @@
|
||||
typedef NSFetchRequest *(^RKFetchRequestBlock)(NSURL *URL);
|
||||
|
||||
/**
|
||||
Returns a fetch request object from an array of `RKFetchRequestBlock` objects given a URL.
|
||||
Returns an array of fetch request objects from an array of `RKFetchRequestBlock` objects given a URL.
|
||||
|
||||
@param fetchRequestBlocks An array of `RKFetchRequestBlock` blocks to
|
||||
@param URL The URL for which to return a fetch request.
|
||||
@return A fetch request from the first block that matches the URL.
|
||||
@return An array of fetch requests from all blocks that match the given URL.
|
||||
*/
|
||||
NSFetchRequest *RKFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL);
|
||||
NSArray *RKArrayOfFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL);
|
||||
|
||||
@@ -29,14 +29,15 @@
|
||||
#undef RKLogComponent
|
||||
#define RKLogComponent RKlcl_cRestKitCoreData
|
||||
|
||||
NSFetchRequest *RKFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL)
|
||||
NSArray *RKArrayOfFetchRequestFromBlocksWithURL(NSArray *fetchRequestBlocks, NSURL *URL)
|
||||
{
|
||||
NSMutableArray *fetchRequests = [NSMutableArray array];
|
||||
NSFetchRequest *fetchRequest = nil;
|
||||
for (RKFetchRequestBlock block in [fetchRequestBlocks reverseObjectEnumerator]) {
|
||||
fetchRequest = block(URL);
|
||||
if (fetchRequest) break;
|
||||
if (fetchRequest) [fetchRequests addObject:fetchRequest];
|
||||
}
|
||||
return fetchRequest;
|
||||
return fetchRequests;
|
||||
}
|
||||
|
||||
static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManagedObjects(NSDictionary *dictionaryOfManagedObjects, NSManagedObjectContext *managedObjectContext)
|
||||
@@ -69,6 +70,20 @@ static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManage
|
||||
return newDictionary;
|
||||
}
|
||||
|
||||
static NSURL *RKRelativeURLFromURLAndResponseDescriptors(NSURL *URL, NSArray *responseDescriptors)
|
||||
{
|
||||
NSCParameterAssert(URL);
|
||||
NSCParameterAssert(responseDescriptors);
|
||||
NSArray *baseURLs = [responseDescriptors valueForKeyPath:@"@distinctUnionOfObjects.baseURL"];
|
||||
if ([baseURLs count] == 1) {
|
||||
NSURL *baseURL = baseURLs[0];
|
||||
NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL(URL, baseURL);
|
||||
URL = [NSURL URLWithString:pathAndQueryString relativeToURL:baseURL];
|
||||
}
|
||||
|
||||
return URL;
|
||||
}
|
||||
|
||||
@interface RKManagedObjectRequestOperation () <RKMapperOperationDelegate>
|
||||
// Core Data specific
|
||||
@property (nonatomic, strong) NSManagedObjectContext *privateContext;
|
||||
@@ -134,8 +149,21 @@ static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManage
|
||||
{
|
||||
if (self.HTTPRequestOperation.wasNotModified) {
|
||||
RKLogDebug(@"Managed object mapping requested for cached response: skipping mapping...");
|
||||
// TODO: This is unexpectedly returning an empty result set... need to be able to retrieve the appropriate objects...
|
||||
return [[RKMappingResult alloc] initWithDictionary:@{}];
|
||||
NSURL *URL = RKRelativeURLFromURLAndResponseDescriptors(self.HTTPRequestOperation.response.URL, self.responseDescriptors);
|
||||
NSArray *fetchRequests = RKArrayOfFetchRequestFromBlocksWithURL(self.fetchRequestBlocks, URL);
|
||||
NSMutableArray *managedObjects = [NSMutableArray array];
|
||||
[self.managedObjectContext performBlockAndWait:^{
|
||||
NSError *error = nil;
|
||||
for (NSFetchRequest *fetchRequest in fetchRequests) {
|
||||
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
if (fetchedObjects) {
|
||||
[managedObjects addObjectsFromArray:fetchedObjects];
|
||||
} else {
|
||||
RKLogError(@"Failed to execute fetch request %@: %@", fetchRequest, error);
|
||||
}
|
||||
}
|
||||
}];
|
||||
return [[RKMappingResult alloc] initWithDictionary:@{ [NSNull null]: managedObjects }];
|
||||
}
|
||||
|
||||
self.responseMapperOperation = [[RKManagedObjectResponseMapperOperation alloc] initWithResponse:self.HTTPRequestOperation.response
|
||||
@@ -190,14 +218,7 @@ static NSDictionary *RKDictionaryOfManagedObjectsInContextFromDictionaryOfManage
|
||||
__block NSArray *_blockObjects;
|
||||
|
||||
// Pass the fetch request blocks a relative `NSURL` object if possible
|
||||
NSURL *URL = [self.HTTPRequestOperation.request URL];
|
||||
NSArray *baseURLs = [self.responseDescriptors valueForKeyPath:@"@distinctUnionOfObjects.baseURL"];
|
||||
if ([baseURLs count] == 1) {
|
||||
NSURL *baseURL = baseURLs[0];
|
||||
NSString *pathAndQueryString = RKPathAndQueryStringFromURLRelativeToURL(URL, baseURL);
|
||||
URL = [NSURL URLWithString:pathAndQueryString relativeToURL:baseURL];
|
||||
}
|
||||
|
||||
NSURL *URL = RKRelativeURLFromURLAndResponseDescriptors(self.HTTPRequestOperation.response.URL, self.responseDescriptors);
|
||||
for (RKFetchRequestBlock fetchRequestBlock in [self.fetchRequestBlocks reverseObjectEnumerator]) {
|
||||
NSFetchRequest *fetchRequest = fetchRequestBlock(URL);
|
||||
if (fetchRequest) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "RKTestEnvironment.h"
|
||||
#import "RKManagedObjectRequestOperation.h"
|
||||
#import "RKEntityMapping.h"
|
||||
#import "RKHuman.h"
|
||||
|
||||
@interface RKManagedObjectRequestOperation ()
|
||||
- (NSSet *)localObjectsFromFetchRequestsMatchingRequestURL:(NSError **)error;
|
||||
@@ -21,6 +22,16 @@
|
||||
|
||||
@implementation RKManagedObjectRequestOperationTest
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[RKTestFactory setUp];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
[RKTestFactory tearDown];
|
||||
}
|
||||
|
||||
- (void)testThatInitializationWithRequestDefaultsToSavingToPersistentStore
|
||||
{
|
||||
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:@"http://restkit.org"]];
|
||||
@@ -42,10 +53,15 @@
|
||||
{
|
||||
NSURL *baseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/"];
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"categories/1234" relativeToURL:baseURL]];
|
||||
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:@{}];
|
||||
RKObjectMapping *mapping = [RKObjectMapping requestMapping];
|
||||
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"categories/:categoryID" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
|
||||
responseDescriptor.baseURL = baseURL;
|
||||
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]];
|
||||
id mockRequestOperation = [OCMockObject niceMockForClass:[RKHTTPRequestOperation class]];
|
||||
[[[mockRequestOperation stub] andReturn:request] request];
|
||||
[[[mockRequestOperation stub] andReturn:response] response];
|
||||
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithHTTPRequestOperation:mockRequestOperation responseDescriptors:@[ responseDescriptor ]];
|
||||
|
||||
__block NSURL *blockURL = nil;
|
||||
RKFetchRequestBlock fetchRequesBlock = ^NSFetchRequest *(NSURL *URL) {
|
||||
blockURL = URL;
|
||||
@@ -64,11 +80,15 @@
|
||||
{
|
||||
NSURL *baseURL = [NSURL URLWithString:@"http://restkit.org/api/v1/"];
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"library/" relativeToURL:baseURL]];
|
||||
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:@{}];
|
||||
expect([request.URL absoluteString]).to.equal(@"http://restkit.org/api/v1/library/");
|
||||
RKObjectMapping *mapping = [RKObjectMapping requestMapping];
|
||||
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping pathPattern:@"library/" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
|
||||
responseDescriptor.baseURL = baseURL;
|
||||
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]];
|
||||
id mockRequestOperation = [OCMockObject niceMockForClass:[RKHTTPRequestOperation class]];
|
||||
[[[mockRequestOperation stub] andReturn:request] request];
|
||||
[[[mockRequestOperation stub] andReturn:response] response];
|
||||
RKManagedObjectRequestOperation *operation = [[RKManagedObjectRequestOperation alloc] initWithHTTPRequestOperation:mockRequestOperation responseDescriptors:@[ responseDescriptor ]];
|
||||
__block NSURL *blockURL = nil;
|
||||
RKFetchRequestBlock fetchRequesBlock = ^NSFetchRequest *(NSURL *URL) {
|
||||
blockURL = URL;
|
||||
@@ -87,7 +107,6 @@
|
||||
|
||||
- (void)testThatMappingResultContainsObjectsFetchedFromManagedObjectContextTheOperationWasInitializedWith
|
||||
{
|
||||
// void RKSetIntermediateDictionaryValuesOnObjectForKeyPath(id object, NSString *keyPath);
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/JSON/humans/all.json" relativeToURL:[RKTestFactory baseURL]]];
|
||||
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
|
||||
RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"RKHuman" inManagedObjectStore:managedObjectStore];
|
||||
@@ -105,4 +124,30 @@
|
||||
expect(managedObjectContexts[0]).to.equal(managedObjectStore.mainQueueManagedObjectContext);
|
||||
}
|
||||
|
||||
// 304 'Not Modified'
|
||||
- (void)testThatManagedObjectsAreFetchedWhenHandlingANotModifiedResponse
|
||||
{
|
||||
RKFetchRequestBlock fetchRequestBlock = ^(NSURL *URL){
|
||||
return [NSFetchRequest fetchRequestWithEntityName:@"RKHuman"];
|
||||
};
|
||||
|
||||
RKManagedObjectStore *managedObjectStore = [RKTestFactory managedObjectStore];
|
||||
RKHuman *human = [NSEntityDescription insertNewObjectForEntityForName:@"RKHuman" inManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext];
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/204_with_not_modified_status" relativeToURL:[RKTestFactory baseURL]]];
|
||||
RKEntityMapping *humanMapping = [RKEntityMapping mappingForEntityForName:@"RKHuman" inManagedObjectStore:managedObjectStore];
|
||||
[humanMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
|
||||
|
||||
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:humanMapping pathPattern:nil keyPath:@"human" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
|
||||
RKManagedObjectRequestOperation *managedObjectRequestOperation = [[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]];
|
||||
managedObjectRequestOperation.fetchRequestBlocks = @[fetchRequestBlock];
|
||||
managedObjectRequestOperation.managedObjectContext = managedObjectStore.mainQueueManagedObjectContext;
|
||||
|
||||
[managedObjectRequestOperation start];
|
||||
[managedObjectRequestOperation waitUntilFinished];
|
||||
expect(managedObjectRequestOperation.mappingResult).notTo.beNil();
|
||||
NSArray *mappedObjects = [managedObjectRequestOperation.mappingResult array];
|
||||
expect(mappedObjects).to.haveCountOf(1);
|
||||
expect(mappedObjects[0]).to.equal(human);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
9
Tests/Server/server.rb
Normal file → Executable file
9
Tests/Server/server.rb
Normal file → Executable file
@@ -268,6 +268,15 @@ class RestKitTestServer < Sinatra::Base
|
||||
sleep 0.05
|
||||
status 204
|
||||
end
|
||||
|
||||
get '/304' do
|
||||
status 304
|
||||
end
|
||||
|
||||
get '/204_with_not_modified_status' do
|
||||
status 204
|
||||
response.headers['Status'] = '304 Not Modified'
|
||||
end
|
||||
|
||||
# start the server if ruby file executed directly
|
||||
run! if app_file == $0
|
||||
|
||||
Reference in New Issue
Block a user