Implement Timeout Based Caches

This commit is contained in:
Jeremy Ellison
2011-06-27 12:39:17 -04:00
committed by Blake Watters
parent 16f6f004b5
commit f98566b837
6 changed files with 146 additions and 24 deletions

View File

@@ -45,8 +45,11 @@ typedef enum {
// Load from the cache if we have data stored // Load from the cache if we have data stored
RKRequestCachePolicyEnabled = 1 << 3, RKRequestCachePolicyEnabled = 1 << 3,
// Load from the cache if we are within the timeout window
RKRequestCachePolicyTimeout = 1 << 4,
RKRequestCachePolicyDefault = RKRequestCachePolicyEtag RKRequestCachePolicyDefault = RKRequestCachePolicyEtag | RKRequestCachePolicyTimeout
} RKRequestCachePolicy; } RKRequestCachePolicy;
/** /**
@@ -86,6 +89,7 @@ typedef enum RKRequestBackgroundPolicy {
BOOL _sentSynchronously; BOOL _sentSynchronously;
BOOL _forceBasicAuthentication; BOOL _forceBasicAuthentication;
RKRequestCache* _cache; RKRequestCache* _cache;
NSTimeInterval _cacheTimeoutInterval;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
RKRequestBackgroundPolicy _backgroundPolicy; RKRequestBackgroundPolicy _backgroundPolicy;
@@ -181,6 +185,13 @@ typedef enum RKRequestBackgroundPolicy {
*/ */
@property (nonatomic, retain) NSString* HTTPBodyString; @property (nonatomic, retain) NSString* HTTPBodyString;
/**
* The timeout interval within which the request should not be sent
* and the cached response should be used. Used if the cache policy
* includes RKRequestCachePolicyTimeout
*/
@property (nonatomic, assign) NSTimeInterval cacheTimeoutInterval;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -27,7 +27,8 @@
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders, @synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders,
params = _params, userData = _userData, username = _username, password = _password, method = _method, params = _params, userData = _userData, username = _username, password = _password, method = _method,
forceBasicAuthentication = _forceBasicAuthentication, cachePolicy = _cachePolicy, cache = _cache; forceBasicAuthentication = _forceBasicAuthentication, cachePolicy = _cachePolicy, cache = _cache,
cacheTimeoutInterval = _cacheTimeoutInterval;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@synthesize backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier; @synthesize backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier;
@@ -44,6 +45,7 @@
[self reset]; [self reset];
_forceBasicAuthentication = NO; _forceBasicAuthentication = NO;
_cachePolicy = RKRequestCachePolicyDefault; _cachePolicy = RKRequestCachePolicyDefault;
_cacheTimeoutInterval = 0;
} }
return self; return self;
} }
@@ -268,23 +270,37 @@
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
} }
- (BOOL)shouldDispatchRequest { - (BOOL)shouldLoadFromCache {
// if RKRequestCachePolicyEnabled or if RKRequestCachePolicyTimeout and we are in the timeout
if ([self.cache hasResponseForRequest:self]) {
if (self.cachePolicy & RKRequestCachePolicyEnabled) {
return YES;
} else if (self.cachePolicy & RKRequestCachePolicyTimeout) {
NSDate* date = [self.cache cacheDateForRequest:self];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:date];
return interval <= self.cacheTimeoutInterval;
}
}
return NO;
}
- (RKResponse*)loadResponseFromCache {
RKLogDebug(@"Found cached content, loading...");
return [self.cache responseForRequest:self];
}
- (BOOL)shouldDispatchRequest {
return [RKClient sharedClient] == nil || [[RKClient sharedClient] isNetworkAvailable]; return [RKClient sharedClient] == nil || [[RKClient sharedClient] isNetworkAvailable];
} }
- (void)sendAsynchronously { - (void)sendAsynchronously {
NSAssert(NO == _isLoading || NO == _isLoaded, @"Cannot send a request that is loading or loaded without resetting it first."); NSAssert(NO == _isLoading || NO == _isLoaded, @"Cannot send a request that is loading or loaded without resetting it first.");
_sentSynchronously = NO; _sentSynchronously = NO;
if (self.cachePolicy & RKRequestCachePolicyEnabled) { if ([self shouldLoadFromCache]) {
if ([self.cache hasResponseForRequest:self]) { RKResponse* response = [self loadResponseFromCache];
RKLogDebug(@"Found cached content, loading..."); _isLoading = YES;
_isLoading = YES; [self didFinishLoad:response];
[self didFinishLoad:[self.cache responseForRequest:self]]; } else if ([self shouldDispatchRequest]) {
return;
}
}
if ([self shouldDispatchRequest]) {
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
// Background Request Policy support // Background Request Policy support
UIApplication* app = [UIApplication sharedApplication]; UIApplication* app = [UIApplication sharedApplication];
@@ -327,7 +343,7 @@
[self.cache hasResponseForRequest:self]) { [self.cache hasResponseForRequest:self]) {
_isLoading = YES; _isLoading = YES;
[self didFinishLoad:[self.cache responseForRequest:self]]; [self didFinishLoad:[self loadResponseFromCache]];
} else { } else {
RKLogCritical(@"SharedClient = %@ and network availability = %d", [RKClient sharedClient], [[RKClient sharedClient] isNetworkAvailable]); RKLogCritical(@"SharedClient = %@ and network availability = %d", [RKClient sharedClient], [[RKClient sharedClient] isNetworkAvailable]);
@@ -349,12 +365,16 @@
RKResponse* response = nil; RKResponse* response = nil;
_sentSynchronously = YES; _sentSynchronously = YES;
if ([self shouldDispatchRequest]) { if ([self shouldLoadFromCache]) {
RKLogDebug(@"Sending synchronous %@ request to URL %@.", [self HTTPMethod], [[self URL] absoluteString]); response = [self loadResponseFromCache];
if (![self prepareURLRequest]) { _isLoading = YES;
// TODO: Logging [self didFinishLoad:response];
return nil; } else if ([self shouldDispatchRequest]) {
} RKLogDebug(@"Sending synchronous %@ request to URL %@.", [self HTTPMethod], [[self URL] absoluteString]);
if (![self prepareURLRequest]) {
// TODO: Logging
return nil;
}
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
@@ -378,7 +398,7 @@
if (_cachePolicy & RKRequestCachePolicyLoadIfOffline && if (_cachePolicy & RKRequestCachePolicyLoadIfOffline &&
[self.cache hasResponseForRequest:self]) { [self.cache hasResponseForRequest:self]) {
response = [self.cache responseForRequest:self]; response = [self loadResponseFromCache];
} else { } else {
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]]; NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
@@ -404,7 +424,7 @@
if (_cachePolicy & RKRequestCachePolicyLoadOnError && if (_cachePolicy & RKRequestCachePolicyLoadOnError &&
[self.cache hasResponseForRequest:self]) { [self.cache hasResponseForRequest:self]) {
[self didFinishLoad:[self.cache responseForRequest:self]]; [self didFinishLoad:[self loadResponseFromCache]];
} else { } else {
_isLoading = NO; _isLoading = NO;
@@ -429,7 +449,7 @@
RKResponse* finalResponse = response; RKResponse* finalResponse = response;
if ((_cachePolicy & RKRequestCachePolicyEtag) && [response isNotModified]) { if ((_cachePolicy & RKRequestCachePolicyEtag) && [response isNotModified]) {
finalResponse = [self.cache responseForRequest:self]; finalResponse = [self loadResponseFromCache];
} }
if (![response wasLoadedFromCache] && [response isSuccessful] && (_cachePolicy != RKRequestCachePolicyNone)) { if (![response wasLoadedFromCache] && [response isSuccessful] && (_cachePolicy != RKRequestCachePolicyNone)) {

View File

@@ -48,6 +48,8 @@ typedef enum {
- (NSString*)etagForRequest:(RKRequest*)request; - (NSString*)etagForRequest:(RKRequest*)request;
- (NSDate*)cacheDateForRequest:(RKRequest*)request;
- (void)invalidateRequest:(RKRequest*)request; - (void)invalidateRequest:(RKRequest*)request;
- (void)invalidateWithStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy; - (void)invalidateWithStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy;

View File

@@ -245,6 +245,27 @@ static NSDateFormatter* __rfc1123DateFormatter;
return etag; return etag;
} }
- (NSDate*)cacheDateForRequest:(RKRequest*)request {
NSDate* date;
NSString* dateString;
NSDictionary* responseHeaders = [self headersForRequest:request];
[_cacheLock lock];
if (responseHeaders) {
for (NSString* responseHeader in responseHeaders) {
if ([[responseHeader uppercaseString] isEqualToString:[cacheDateHeaderKey uppercaseString]]) {
dateString = [responseHeaders objectForKey:responseHeader];
}
}
}
[_cacheLock unlock];
date = [[RKRequestCache rfc1123DateFormatter] dateFromString:dateString];
RKLogDebug(@"Found cached date '%@' for '%@'", date, request);
return date;
}
- (void)invalidateRequest:(RKRequest*)request { - (void)invalidateRequest:(RKRequest*)request {
[_cacheLock lock]; [_cacheLock lock];
RKLogDebug(@"Invalidating cache entry for '%@'", request); RKLogDebug(@"Invalidating cache entry for '%@'", request);

View File

@@ -324,6 +324,72 @@
} }
} }
- (void)itShouldLoadFromTheCacheIfWeAreWithinTheTimeout {
RKLogConfigureByName("RestKit/Network", RKLogLevelTrace);
NSString* baseURL = RKSpecGetBaseURL();
NSString* cacheDirForClient = [NSString stringWithFormat:@"RKClientRequestCache-%@",
[[NSURL URLWithString:baseURL] host]];
NSString* cachePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent:cacheDirForClient];
RKRequestCache* cache = [[RKRequestCache alloc] initWithCachePath:cachePath
storagePolicy:RKRequestCacheStoragePolicyPermanently];
[cache invalidateWithStoragePolicy:RKRequestCacheStoragePolicyPermanently];
NSString* url = [NSString stringWithFormat:@"%@/disk/cached", RKSpecGetBaseURL()];
NSURL* URL = [NSURL URLWithString:url];
{
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
request.cachePolicy = RKRequestCachePolicyTimeout;
request.cacheTimeoutInterval = 5;
request.cache = cache;
request.delegate = loader;
[request sendAsynchronously];
[loader waitForResponse];
[expectThat([loader success]) should:be(YES)];
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached For 5 Seconds")];
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
}
{
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
request.cachePolicy = RKRequestCachePolicyTimeout;
request.cacheTimeoutInterval = 5;
request.cache = cache;
request.delegate = loader;
[request sendAsynchronously];
// Don't wait for a response as this actually returns synchronously.
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached For 5 Seconds")];
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
}
{
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
request.cachePolicy = RKRequestCachePolicyTimeout;
request.cacheTimeoutInterval = 5;
request.cache = cache;
request.delegate = loader;
[request sendSynchronously];
// Don't wait for a response as this actually returns synchronously.
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached For 5 Seconds")];
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
}
{
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
request.cachePolicy = RKRequestCachePolicyTimeout;
request.cacheTimeoutInterval = 0;
request.cache = cache;
request.delegate = loader;
[request sendAsynchronously];
[loader waitForResponse];
[expectThat([loader success]) should:be(YES)];
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached For 5 Seconds")];
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
}
}
- (void)itShouldLoadFromTheCacheIfWeAreOffline { - (void)itShouldLoadFromTheCacheIfWeAreOffline {
NSString* baseURL = RKSpecGetBaseURL(); NSString* baseURL = RKSpecGetBaseURL();
NSString* cacheDirForClient = [NSString stringWithFormat:@"RKClientRequestCache-%@", NSString* cacheDirForClient = [NSString stringWithFormat:@"RKClientRequestCache-%@",

View File

@@ -11,11 +11,13 @@ Debugger.start
$: << File.join(File.expand_path(File.dirname(__FILE__)), 'lib') $: << File.join(File.expand_path(File.dirname(__FILE__)), 'lib')
require 'restkit/network/authentication' require 'restkit/network/authentication'
require 'restkit/network/etags' require 'restkit/network/etags'
require 'restkit/network/timeout'
class RestKit::SpecServer < Sinatra::Base class RestKit::SpecServer < Sinatra::Base
self.app_file = __FILE__ self.app_file = __FILE__
use RestKit::Network::Authentication use RestKit::Network::Authentication
use RestKit::Network::ETags use RestKit::Network::ETags
use RestKit::Network::Timeout
configure do configure do
set :logging, true set :logging, true