mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-17 12:20:20 +08:00
Merge Request Queue (See issue #75):
* Introduces RKRequestCache for cacheing responses (supports ETag conditional GET, use cache if available, use cache on error, etc.) closes #75 * Updates to Three20 layer to eliminate need for intermediary TTTableItem classes closes #76 * Fixes to ensure iOS 3.x compatability: * Switched compiler to Clang * Updated conditional checks for UIBackgroundTask symbols to ensure runtime safety on iOS 3.x * Removed unnecessary linkage against UIKit and CoreFoundation from library targets * Fix for issue where RKRequest objects could become stuck in infinite loop within RKRequestQueue loadNextInQueue if you start a request and then cancel immediately. On cancel only decrement loadCount if the request has start loading. refs #122
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc {
|
||||
[_targetObjectID release];
|
||||
_targetObjectID = nil;
|
||||
|
||||
14
Code/Network/NSData+MD5.h
Normal file
14
Code/Network/NSData+MD5.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NSData+MD5.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@interface NSData (MD5)
|
||||
|
||||
- (NSString*)MD5;
|
||||
|
||||
@end
|
||||
30
Code/Network/NSData+MD5.m
Normal file
30
Code/Network/NSData+MD5.m
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// NSData+MD5.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+MD5.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation NSData (MD5)
|
||||
|
||||
- (NSString*)MD5 {
|
||||
// Create byte array of unsigned chars
|
||||
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
|
||||
|
||||
// Create 16 byte MD5 hash value, store in buffer
|
||||
CC_MD5(self.bytes, self.length, md5Buffer);
|
||||
|
||||
// Convert unsigned char buffer to NSString of hex values
|
||||
NSMutableString* output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
||||
[output appendFormat:@"%02x",md5Buffer[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@end
|
||||
14
Code/Network/NSString+MD5.h
Normal file
14
Code/Network/NSString+MD5.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NSString+MD5.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@interface NSString (MD5)
|
||||
|
||||
- (NSString*)MD5;
|
||||
|
||||
@end
|
||||
33
Code/Network/NSString+MD5.m
Normal file
33
Code/Network/NSString+MD5.m
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// NSString+MD5.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+MD5.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation NSString (MD5)
|
||||
|
||||
- (NSString*)MD5 {
|
||||
// Create pointer to the string as UTF8
|
||||
const char* ptr = [self UTF8String];
|
||||
|
||||
// Create byte array of unsigned chars
|
||||
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
|
||||
|
||||
// Create 16 byte MD5 hash value, store in buffer
|
||||
CC_MD5(ptr, strlen(ptr), md5Buffer);
|
||||
|
||||
// Convert MD5 value in the buffer to NSString of hex values
|
||||
NSMutableString* output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
||||
[output appendFormat:@"%02x",md5Buffer[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "RKResponse.h"
|
||||
#import "NSDictionary+RKRequestSerialization.h"
|
||||
#import "RKReachabilityObserver.h"
|
||||
#import "RKRequestCache.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -127,6 +128,8 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
NSString* _serviceUnavailableAlertTitle;
|
||||
NSString* _serviceUnavailableAlertMessage;
|
||||
BOOL _serviceUnavailableAlertEnabled;
|
||||
RKRequestCache* _cache;
|
||||
RKRequestCachePolicy _cachePolicy;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@@ -224,6 +227,10 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL serviceUnavailableAlertEnabled;
|
||||
|
||||
@property (nonatomic, retain) RKRequestCache* cache;
|
||||
|
||||
@property (nonatomic, assign) RKRequestCachePolicy cachePolicy;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/// @name Shared Client Instance
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -32,7 +32,7 @@ NSString* RKMakeURLPath(NSString* resourcePath) {
|
||||
NSString* RKMakePathWithObject(NSString* path, id object) {
|
||||
NSMutableDictionary* substitutions = [NSMutableDictionary dictionary];
|
||||
NSScanner* scanner = [NSScanner scannerWithString:path];
|
||||
|
||||
|
||||
BOOL startsWithParentheses = [[path substringToIndex:1] isEqualToString:@"("];
|
||||
while ([scanner isAtEnd] == NO) {
|
||||
NSString* keyPath = nil;
|
||||
@@ -49,20 +49,20 @@ NSString* RKMakePathWithObject(NSString* path, id object) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (0 == [substitutions count]) {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
NSMutableString* interpolatedPath = [[path mutableCopy] autorelease];
|
||||
for (NSString* find in substitutions) {
|
||||
NSString* replace = [substitutions valueForKey:find];
|
||||
[interpolatedPath replaceOccurrencesOfString:find
|
||||
withString:replace
|
||||
options:NSLiteralSearch
|
||||
[interpolatedPath replaceOccurrencesOfString:find
|
||||
withString:replace
|
||||
options:NSLiteralSearch
|
||||
range:NSMakeRange(0, [interpolatedPath length])];
|
||||
}
|
||||
|
||||
|
||||
return [NSString stringWithString:interpolatedPath];
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
@synthesize serviceUnavailableAlertTitle = _serviceUnavailableAlertTitle;
|
||||
@synthesize serviceUnavailableAlertMessage = _serviceUnavailableAlertMessage;
|
||||
@synthesize serviceUnavailableAlertEnabled = _serviceUnavailableAlertEnabled;
|
||||
@synthesize cache = _cache;
|
||||
@synthesize cachePolicy = _cachePolicy;
|
||||
|
||||
+ (RKClient*)sharedClient {
|
||||
return sharedClient;
|
||||
@@ -95,6 +97,13 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
|
||||
+ (RKClient*)clientWithBaseURL:(NSString*)baseURL {
|
||||
RKClient* client = [[[RKClient alloc] init] autorelease];
|
||||
NSString* cacheDirForClient = [NSString stringWithFormat:@"RKClientRequestCache-%@",
|
||||
[[NSURL URLWithString:baseURL] host]];
|
||||
NSString* cachePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]
|
||||
stringByAppendingPathComponent:cacheDirForClient];
|
||||
client.cache = [[RKRequestCache alloc] initWithCachePath:cachePath
|
||||
storagePolicy:RKRequestCacheStoragePolicyPermanently];
|
||||
client.cachePolicy = RKRequestCachePolicyDefault;
|
||||
client.baseURL = baseURL;
|
||||
if (sharedClient == nil) {
|
||||
[RKClient setSharedClient:client];
|
||||
@@ -133,8 +142,9 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
self.password = nil;
|
||||
self.serviceUnavailableAlertTitle = nil;
|
||||
self.serviceUnavailableAlertMessage = nil;
|
||||
self.cache = nil;
|
||||
[_HTTPHeaders release];
|
||||
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -170,6 +180,8 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
request.username = self.username;
|
||||
request.password = self.password;
|
||||
request.forceBasicAuthentication = self.forceBasicAuthentication;
|
||||
request.cachePolicy = self.cachePolicy;
|
||||
request.cache = self.cache;
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString*)value forHTTPHeaderField:(NSString*)header {
|
||||
@@ -183,7 +195,7 @@ NSString* RKPathAppendQueryParams(NSString* resourcePath, NSDictionary* queryPar
|
||||
|
||||
[_baseURLReachabilityObserver release];
|
||||
_baseURLReachabilityObserver = nil;
|
||||
|
||||
|
||||
// Don't crash if baseURL is nil'd out (i.e. dealloc)
|
||||
if (baseURL) {
|
||||
NSURL* URL = [NSURL URLWithString:baseURL];
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
NSString* const RKRequestSentNotification = @"RKRequestSentNotification";
|
||||
NSString* const RKRequestDidLoadResponseNotification = @"RKRequestDidLoadResponseNotification";
|
||||
NSString* const RKRequestFailedWithErrorNotification = @"RKRequestFailedWithErrorNotification";
|
||||
NSString* const RKResponseReceivedNotification = @"RKRespongReceivedNotification";
|
||||
NSString* const RKServiceDidBecomeUnavailableNotification = @"RKServiceDidBecomeUnavailableNotification";
|
||||
NSString* const RKResponseReceivedNotification = @"RKResponseReceivedNotification";
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#import <CoreData/CoreData.h>
|
||||
#import "RKRequestSerializable.h"
|
||||
|
||||
@class RKRequestCache;
|
||||
|
||||
/**
|
||||
* HTTP methods for requests
|
||||
*/
|
||||
@@ -24,6 +26,28 @@ typedef enum RKRequestMethod {
|
||||
RKRequestMethodDELETE
|
||||
} RKRequestMethod;
|
||||
|
||||
/**
|
||||
* Cache policy for determining how to use RKCache
|
||||
*/
|
||||
typedef enum {
|
||||
// Never use the cache
|
||||
RKRequestCachePolicyNone = 0,
|
||||
|
||||
// Load from the cache when we are offline
|
||||
RKRequestCachePolicyLoadIfOffline = 1 << 0,
|
||||
|
||||
// Load from the cache if we encounter an error
|
||||
RKRequestCachePolicyLoadOnError = 1 << 1,
|
||||
|
||||
// Load from the cache if we have data stored and the server returns a 304 (not modified) response
|
||||
RKRequestCachePolicyEtag = 1 << 2,
|
||||
|
||||
// Load from the cache if we have data stored
|
||||
RKRequestCachePolicyEnabled = 1 << 3,
|
||||
|
||||
RKRequestCachePolicyDefault = RKRequestCachePolicyEtag
|
||||
} RKRequestCachePolicy;
|
||||
|
||||
/**
|
||||
* Background Request Policy
|
||||
*
|
||||
@@ -55,9 +79,11 @@ typedef enum RKRequestBackgroundPolicy {
|
||||
RKRequestMethod _method;
|
||||
BOOL _isLoading;
|
||||
BOOL _isLoaded;
|
||||
RKRequestCachePolicy _cachePolicy;
|
||||
BOOL _sentSynchronously;
|
||||
BOOL _forceBasicAuthentication;
|
||||
RKRequestBackgroundPolicy _backgroundPolicy;
|
||||
RKRequestCache* _cache;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
UIBackgroundTaskIdentifier _backgroundTaskIdentifier;
|
||||
@@ -137,6 +163,11 @@ typedef enum RKRequestBackgroundPolicy {
|
||||
*/
|
||||
@property(nonatomic, readonly) NSString* HTTPMethod;
|
||||
|
||||
@property (nonatomic, readonly) NSString* cacheKey;
|
||||
|
||||
@property (nonatomic, assign) RKRequestCachePolicy cachePolicy;
|
||||
@property (nonatomic, retain) RKRequestCache* cache;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -14,13 +14,16 @@
|
||||
#import "RKClient.h"
|
||||
#import "../Support/Support.h"
|
||||
#import "RKURL.h"
|
||||
#import "NSData+MD5.h"
|
||||
#import "NSString+MD5.h"
|
||||
#import "Logging.h"
|
||||
#import "RKRequestCache.h"
|
||||
|
||||
@implementation RKRequest
|
||||
|
||||
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders,
|
||||
params = _params, userData = _userData, username = _username, password = _password, method = _method,
|
||||
forceBasicAuthentication = _forceBasicAuthentication;
|
||||
forceBasicAuthentication = _forceBasicAuthentication, cachePolicy = _cachePolicy, cache = _cache;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
@synthesize backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier;
|
||||
@@ -35,19 +38,20 @@
|
||||
if (self) {
|
||||
_URL = [URL retain];
|
||||
_URLRequest = [[NSMutableURLRequest alloc] initWithURL:_URL];
|
||||
[_URLRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
||||
_connection = nil;
|
||||
_isLoading = NO;
|
||||
_isLoaded = NO;
|
||||
_forceBasicAuthentication = NO;
|
||||
_cachePolicy = RKRequestCachePolicyDefault;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate {
|
||||
self = [self initWithURL:URL];
|
||||
if (self) {
|
||||
_delegate = delegate;
|
||||
_delegate = delegate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -57,7 +61,11 @@
|
||||
if (self) {
|
||||
#if TARGET_OS_IPHONE
|
||||
_backgroundPolicy = RKRequestBackgroundPolicyNone;
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
_backgroundTaskIdentifier = 0;
|
||||
BOOL backgroundOK = &UIBackgroundTaskInvalid != NULL;
|
||||
if (backgroundOK) {
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -66,12 +74,11 @@
|
||||
|
||||
- (void)cleanupBackgroundTask {
|
||||
#if TARGET_OS_IPHONE
|
||||
if (UIBackgroundTaskInvalid == self.backgroundTaskIdentifier) {
|
||||
BOOL backgroundOK = &UIBackgroundTaskInvalid != NULL;
|
||||
if (backgroundOK && UIBackgroundTaskInvalid == self.backgroundTaskIdentifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"Cleaning up background task...");
|
||||
|
||||
UIApplication* app = [UIApplication sharedApplication];
|
||||
if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
|
||||
[app endBackgroundTask:_backgroundTaskIdentifier];
|
||||
@@ -101,6 +108,8 @@
|
||||
_username = nil;
|
||||
[_password release];
|
||||
_password = nil;
|
||||
[_cache release];
|
||||
_cache = nil;
|
||||
|
||||
// Cleanup a background task if there is any
|
||||
[self cleanupBackgroundTask];
|
||||
@@ -147,6 +156,13 @@
|
||||
CFRelease(dummyRequest);
|
||||
CFRelease(authorizationString);
|
||||
}
|
||||
|
||||
if (self.cachePolicy & RKRequestCachePolicyEtag) {
|
||||
NSString* etag = [self.cache etagForRequest:self];
|
||||
if (etag) {
|
||||
[_URLRequest setValue:etag forHTTPHeaderField:@"If-None-Match"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the NSURLRequest. The request must be prepared right before dispatching
|
||||
@@ -215,7 +231,16 @@
|
||||
|
||||
- (void)sendAsynchronously {
|
||||
_sentSynchronously = NO;
|
||||
if ([self shouldDispatchRequest]) {
|
||||
if (self.cachePolicy & RKRequestCachePolicyEnabled) {
|
||||
if ([self.cache hasResponseForRequest:self]) {
|
||||
NSLog(@"Found cached content, loading...");
|
||||
_isLoading = YES;
|
||||
[self didFinishLoad:[self.cache responseForRequest:self]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ([self shouldDispatchRequest]) {
|
||||
#if TARGET_OS_IPHONE
|
||||
// Background Request Policy support
|
||||
UIApplication* app = [UIApplication sharedApplication];
|
||||
@@ -252,12 +277,20 @@
|
||||
[self fireAsynchronousRequest];
|
||||
#endif
|
||||
} else {
|
||||
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
errorMessage, NSLocalizedDescriptionKey,
|
||||
nil];
|
||||
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
||||
[self didFailLoadWithError:error];
|
||||
if (_cachePolicy & RKRequestCachePolicyLoadIfOffline &&
|
||||
[self.cache hasResponseForRequest:self]) {
|
||||
|
||||
_isLoading = YES;
|
||||
[self didFinishLoad:[self.cache responseForRequest:self]];
|
||||
|
||||
} else {
|
||||
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
errorMessage, NSLocalizedDescriptionKey,
|
||||
nil];
|
||||
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
||||
[self didFailLoadWithError:error];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,16 +324,24 @@
|
||||
} else {
|
||||
[self didFinishLoad:response];
|
||||
}
|
||||
|
||||
} else {
|
||||
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
errorMessage, NSLocalizedDescriptionKey,
|
||||
nil];
|
||||
error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
||||
[self didFailLoadWithError:error];
|
||||
if (_cachePolicy & RKRequestCachePolicyLoadIfOffline &&
|
||||
[self.cache hasResponseForRequest:self]) {
|
||||
|
||||
// TODO: Is this needed here? Or can we just return a nil response and everyone will be happy??
|
||||
response = [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
|
||||
response = [self.cache responseForRequest:self];
|
||||
|
||||
} else {
|
||||
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
errorMessage, NSLocalizedDescriptionKey,
|
||||
nil];
|
||||
error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
|
||||
[self didFailLoadWithError:error];
|
||||
|
||||
// TODO: Is this needed here? Or can we just return a nil response and everyone will be happy??
|
||||
response = [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -310,32 +351,50 @@
|
||||
[self cancelAndInformDelegate:YES];
|
||||
}
|
||||
|
||||
// TODO: Isn't this code duplicated higher up???
|
||||
- (void)didFailLoadWithError:(NSError*)error {
|
||||
_isLoading = NO;
|
||||
if (_cachePolicy & RKRequestCachePolicyLoadOnError &&
|
||||
[self.cache hasResponseForRequest:self]) {
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
|
||||
[_delegate request:self didFailLoadWithError:error];
|
||||
[self didFinishLoad:[self.cache responseForRequest:self]];
|
||||
} else {
|
||||
_isLoading = NO;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
|
||||
[_delegate request:self didFailLoadWithError:error];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestFailedWithErrorNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestFailedWithErrorNotification object:self userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)didFinishLoad:(RKResponse*)response {
|
||||
_isLoading = NO;
|
||||
_isLoaded = YES;
|
||||
|
||||
RKLOG_NETWORK(RKLogLevelInfo, @"Status Code: %d", [response statusCode]);
|
||||
RKLOG_NETWORK(RKLogLevelInfo, @"Body: %@", [response bodyAsString]);
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
|
||||
[_delegate request:self didLoadResponse:response];
|
||||
}
|
||||
|
||||
RKResponse* finalResponse = response;
|
||||
|
||||
if ((_cachePolicy & RKRequestCachePolicyEtag) && [response isNotModified]) {
|
||||
finalResponse = [self.cache responseForRequest:self];
|
||||
}
|
||||
|
||||
if (![response wasLoadedFromCache] && [response isSuccessful] && (_cachePolicy != RKRequestCachePolicyNone)) {
|
||||
[self.cache storeResponse:response forRequest:self];
|
||||
}
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
|
||||
[_delegate request:self didLoadResponse:finalResponse];
|
||||
}
|
||||
|
||||
if ([response isServiceUnavailable]) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self];
|
||||
}
|
||||
|
||||
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:response forKey:@"response"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification object:self userInfo:userInfo];
|
||||
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:finalResponse forKey:@"response"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification object:self userInfo:userInfo];
|
||||
|
||||
// NOTE: This notification must be posted last as the request queue releases the request when it
|
||||
// receives the notification
|
||||
@@ -392,4 +451,12 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSString*)cacheKey {
|
||||
if (_method == RKRequestMethodDELETE) {
|
||||
return nil;
|
||||
}
|
||||
NSString* compositCacheKey = [NSString stringWithFormat:@"%@-%d-%@", self.URL, _method, [_URLRequest HTTPBody]];
|
||||
return [compositCacheKey MD5];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
57
Code/Network/RKRequestCache.h
Normal file
57
Code/Network/RKRequestCache.h
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// RKRequestCache.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequest.h"
|
||||
#import "RKResponse.h"
|
||||
|
||||
|
||||
/**
|
||||
* Storage policy. Determines if we clear the cache out when the app is shut down.
|
||||
* Cache instance needs to register for
|
||||
*/
|
||||
typedef enum {
|
||||
RKRequestCacheStoragePolicyDisabled, // The cache has been disabled. Attempts to store data will silently fail
|
||||
RKRequestCacheStoragePolicyForDurationOfSession, // Cache data for the length of the session. Clear cache at app exit.
|
||||
RKRequestCacheStoragePolicyPermanently // Cache data permanently, until explicitly expired or flushed
|
||||
} RKRequestCacheStoragePolicy;
|
||||
|
||||
|
||||
@interface RKRequestCache : NSObject {
|
||||
NSString* _cachePath;
|
||||
RKRequestCacheStoragePolicy _storagePolicy;
|
||||
NSRecursiveLock* _cacheLock;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) NSString* cachePath; // Full path to the cache
|
||||
@property (nonatomic, assign) RKRequestCacheStoragePolicy storagePolicy; // User can change storage policy.
|
||||
|
||||
+ (NSDateFormatter*)rfc1123DateFormatter;
|
||||
|
||||
- (id)initWithCachePath:(NSString*)cachePath storagePolicy:(RKRequestCacheStoragePolicy)storagePolicy;
|
||||
|
||||
- (NSString*)pathForRequest:(RKRequest*)request;
|
||||
|
||||
- (BOOL)hasResponseForRequest:(RKRequest*)request;
|
||||
|
||||
- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request;
|
||||
|
||||
//- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request expires:(NSTimeInterval)expirationAge;
|
||||
|
||||
- (RKResponse*)responseForRequest:(RKRequest*)request;
|
||||
|
||||
- (NSDictionary*)headersForRequest:(RKRequest*)request;
|
||||
|
||||
- (NSString*)etagForRequest:(RKRequest*)request;
|
||||
|
||||
- (void)invalidateRequest:(RKRequest*)request;
|
||||
|
||||
- (void)invalidateWithStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy;
|
||||
|
||||
- (void)invalidateAll;
|
||||
|
||||
@end
|
||||
285
Code/Network/RKRequestCache.m
Normal file
285
Code/Network/RKRequestCache.m
Normal file
@@ -0,0 +1,285 @@
|
||||
//
|
||||
// RKRequestCache.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestCache.h"
|
||||
|
||||
|
||||
static NSString* sessionCacheFolder = @"SessionStore";
|
||||
static NSString* permanentCacheFolder = @"PermanentStore";
|
||||
static NSString* headersExtension = @"headers";
|
||||
static NSString* cacheDateHeaderKey = @"X-RESTKIT-CACHEDATE";
|
||||
NSString* cacheResponseCodeKey = @"X-RESTKIT-CACHED-RESPONSE-CODE";
|
||||
NSString* cacheMIMETypeKey = @"X-RESTKIT-CACHED-MIME-TYPE";
|
||||
NSString* cacheURLKey = @"X-RESTKIT-CACHED-URL";
|
||||
|
||||
static NSDateFormatter* __rfc1123DateFormatter;
|
||||
|
||||
@implementation RKRequestCache
|
||||
|
||||
@synthesize storagePolicy = _storagePolicy;
|
||||
|
||||
+ (NSDateFormatter*)rfc1123DateFormatter {
|
||||
if (__rfc1123DateFormatter == nil) {
|
||||
__rfc1123DateFormatter = [[NSDateFormatter alloc] init];
|
||||
[__rfc1123DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
[__rfc1123DateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"];
|
||||
}
|
||||
return __rfc1123DateFormatter;
|
||||
}
|
||||
|
||||
- (id)initWithCachePath:(NSString*)cachePath storagePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
if ((self = [super init])) {
|
||||
_cachePath = [cachePath copy];
|
||||
_cacheLock = [[NSRecursiveLock alloc] init];
|
||||
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
NSArray* pathArray = [NSArray arrayWithObjects:
|
||||
_cachePath,
|
||||
[_cachePath stringByAppendingPathComponent:sessionCacheFolder],
|
||||
[_cachePath stringByAppendingPathComponent:permanentCacheFolder],
|
||||
nil];
|
||||
|
||||
for (NSString* path in pathArray) {
|
||||
BOOL isDirectory = NO;
|
||||
BOOL fileExists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
if (!fileExists) {
|
||||
NSError* error = nil;
|
||||
BOOL created = [fileManager createDirectoryAtPath:path
|
||||
withIntermediateDirectories:NO
|
||||
attributes:nil
|
||||
error:&error];
|
||||
if (!created || error != nil) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to create cache directory at %@: error %@",
|
||||
path, [error localizedDescription]);
|
||||
}
|
||||
} else if (!isDirectory) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to create cache directory: Directory already exists: %@", path);
|
||||
}
|
||||
}
|
||||
|
||||
[fileManager release];
|
||||
|
||||
self.storagePolicy = storagePolicy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_cachePath release];
|
||||
_cachePath = nil;
|
||||
[_cacheLock release];
|
||||
_cacheLock = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString*)cachePath {
|
||||
return _cachePath;
|
||||
}
|
||||
|
||||
- (NSString*)pathForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
NSString* pathForRequest = nil;
|
||||
|
||||
if (_storagePolicy == RKRequestCacheStoragePolicyForDurationOfSession) {
|
||||
pathForRequest = [[_cachePath stringByAppendingPathComponent:sessionCacheFolder]
|
||||
stringByAppendingPathComponent:[request cacheKey]];
|
||||
|
||||
} else if (_storagePolicy == RKRequestCacheStoragePolicyPermanently) {
|
||||
pathForRequest = [[_cachePath stringByAppendingPathComponent:permanentCacheFolder]
|
||||
stringByAppendingPathComponent:[request cacheKey]];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return pathForRequest;
|
||||
}
|
||||
|
||||
- (BOOL)hasResponseForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
BOOL hasEntryForRequest = NO;
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
hasEntryForRequest = ([fileManager fileExistsAtPath:cachePath] &&
|
||||
[fileManager fileExistsAtPath:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]]);
|
||||
|
||||
[fileManager release];
|
||||
|
||||
[_cacheLock unlock];
|
||||
return hasEntryForRequest;
|
||||
}
|
||||
|
||||
- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
if ([self hasResponseForRequest:request]) {
|
||||
[self invalidateRequest:request];
|
||||
}
|
||||
|
||||
if (_storagePolicy != RKRequestCacheStoragePolicyDisabled) {
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSData* body = response.body;
|
||||
if (body) {
|
||||
[body writeToFile:cachePath atomically:NO];
|
||||
}
|
||||
|
||||
NSMutableDictionary* headers = [response.allHeaderFields mutableCopy];
|
||||
if (headers) {
|
||||
// TODO: exponse this?
|
||||
NSHTTPURLResponse* urlResponse = [response valueForKey:@"_httpURLResponse"];
|
||||
// Cache Loaded Time
|
||||
[headers setObject:[[RKRequestCache rfc1123DateFormatter] stringFromDate:[NSDate date]]
|
||||
forKey:cacheDateHeaderKey];
|
||||
// Cache status code
|
||||
[headers setObject:[NSNumber numberWithInt:urlResponse.statusCode]
|
||||
forKey:cacheResponseCodeKey];
|
||||
// Cache MIME Type
|
||||
[headers setObject:urlResponse.MIMEType
|
||||
forKey:cacheMIMETypeKey];
|
||||
// // Cache URL
|
||||
[headers setObject:[urlResponse.URL absoluteString]
|
||||
forKey:cacheURLKey];
|
||||
// Save
|
||||
[headers writeToFile:[cachePath stringByAppendingPathExtension:headersExtension]
|
||||
atomically:YES];
|
||||
}
|
||||
[headers release];
|
||||
}
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (RKResponse*)responseForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
RKResponse* response = nil;
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSData* responseData = [NSData dataWithContentsOfFile:cachePath];
|
||||
|
||||
NSDictionary* responseHeaders = [NSDictionary dictionaryWithContentsOfFile:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]];
|
||||
|
||||
response = [[[RKResponse alloc] initWithRequest:request body:responseData headers:responseHeaders] autorelease];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return response;
|
||||
}
|
||||
|
||||
- (NSDictionary*)headersForRequest:(RKRequest*)request {
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
|
||||
[_cacheLock lock];
|
||||
|
||||
NSDictionary* headers = nil;
|
||||
if (cachePath) {
|
||||
headers = [NSDictionary dictionaryWithContentsOfFile:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]];
|
||||
}
|
||||
[_cacheLock unlock];
|
||||
return headers;
|
||||
}
|
||||
|
||||
- (NSString*)etagForRequest:(RKRequest*)request {
|
||||
NSString* etag = nil;
|
||||
|
||||
NSDictionary* responseHeaders = [self headersForRequest:request];
|
||||
|
||||
[_cacheLock lock];
|
||||
if (responseHeaders) {
|
||||
for (NSString* responseHeader in responseHeaders) {
|
||||
if ([[responseHeader uppercaseString] isEqualToString:[@"ETag" uppercaseString]]) {
|
||||
etag = [responseHeaders objectForKey:responseHeader];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return etag;
|
||||
}
|
||||
|
||||
- (void)invalidateRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
[fileManager removeItemAtPath:cachePath error:NULL];
|
||||
[fileManager removeItemAtPath:[cachePath stringByAppendingPathExtension:headersExtension]
|
||||
error:NULL];
|
||||
[fileManager release];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)invalidateWithStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
[_cacheLock lock];
|
||||
|
||||
if (_cachePath && storagePolicy != RKRequestCacheStoragePolicyDisabled) {
|
||||
NSString* cachePath = nil;
|
||||
if (storagePolicy == RKRequestCacheStoragePolicyForDurationOfSession) {
|
||||
cachePath = [_cachePath stringByAppendingPathComponent:sessionCacheFolder];
|
||||
} else {
|
||||
cachePath = [_cachePath stringByAppendingPathComponent:permanentCacheFolder];
|
||||
}
|
||||
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL fileExists = [fileManager fileExistsAtPath:cachePath isDirectory:&isDirectory];
|
||||
|
||||
if (fileExists && isDirectory) {
|
||||
NSError* error = nil;
|
||||
NSArray* cacheEntries = [fileManager contentsOfDirectoryAtPath:cachePath error:&error];
|
||||
|
||||
if (nil == error) {
|
||||
for (NSString* cacheEntry in cacheEntries) {
|
||||
NSString* cacheEntryPath = [cachePath stringByAppendingPathComponent:cacheEntry];
|
||||
[fileManager removeItemAtPath:cacheEntryPath error:&error];
|
||||
[fileManager removeItemAtPath:[cacheEntryPath stringByAppendingPathExtension:headersExtension]
|
||||
error:&error];
|
||||
if (nil != error) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to delete cache entry for file: %@", cacheEntryPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to fetch list of cache entries for cache path: %@", cachePath);
|
||||
}
|
||||
}
|
||||
|
||||
[fileManager release];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)invalidateAll {
|
||||
[_cacheLock lock];
|
||||
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyForDurationOfSession];
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyPermanently];
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)setStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyForDurationOfSession];
|
||||
if (storagePolicy == RKRequestCacheStoragePolicyDisabled) {
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyPermanently];
|
||||
}
|
||||
_storagePolicy = storagePolicy;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -70,14 +70,17 @@ static const NSTimeInterval kFlushDelay = 0.3;
|
||||
name:RKRequestFailedWithErrorNotification
|
||||
object:nil];
|
||||
#if TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(willTransitionToBackground)
|
||||
name:UIApplicationDidEnterBackgroundNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(willTransitionToForeground)
|
||||
name:UIApplicationWillEnterForegroundNotification
|
||||
object:nil];
|
||||
BOOL backgroundOK = &UIApplicationDidEnterBackgroundNotification != NULL;
|
||||
if (backgroundOK) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(willTransitionToBackground)
|
||||
name:UIApplicationDidEnterBackgroundNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(willTransitionToForeground)
|
||||
name:UIApplicationWillEnterForegroundNotification
|
||||
object:nil];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
@@ -145,9 +148,9 @@ static const NSTimeInterval kFlushDelay = 0.3;
|
||||
}
|
||||
|
||||
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
|
||||
_queueTimer = nil;
|
||||
|
||||
|
||||
NSArray* requestsCopy = [NSArray arrayWithArray:_requests];
|
||||
for (RKRequest* request in requestsCopy) {
|
||||
if (![request isLoading] && ![request isLoaded] && self.loadingCount < _concurrentRequestsLimit) {
|
||||
@@ -187,7 +190,7 @@ static const NSTimeInterval kFlushDelay = 0.3;
|
||||
}
|
||||
|
||||
_suspended = isSuspended;
|
||||
|
||||
|
||||
if (!_suspended) {
|
||||
[self loadNextInQueue];
|
||||
} else if (_queueTimer) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
NSHTTPURLResponse* _httpURLResponse;
|
||||
NSMutableData* _body;
|
||||
NSError* _failureError;
|
||||
BOOL _loading;
|
||||
NSDictionary* _responseHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +63,11 @@
|
||||
*/
|
||||
- (id)initWithRequest:(RKRequest*)request;
|
||||
|
||||
/**
|
||||
* Initialize a new response object from a cached request
|
||||
*/
|
||||
- (id)initWithRequest:(RKRequest*)request body:(NSData*)body headers:(NSDictionary*)headers;
|
||||
|
||||
/**
|
||||
* Initializes a response object from the results of a synchronous request
|
||||
*/
|
||||
@@ -86,6 +93,11 @@
|
||||
*/
|
||||
- (NSString*)failureErrorDescription;
|
||||
|
||||
/**
|
||||
* Indicates whether the response was loaded from RKCache
|
||||
*/
|
||||
- (BOOL)wasLoadedFromCache;
|
||||
|
||||
/**
|
||||
* Indicates that the connection failed to reach the remote server. The details of the failure
|
||||
* are available on the failureError reader.
|
||||
@@ -137,6 +149,11 @@
|
||||
*/
|
||||
- (BOOL)isCreated;
|
||||
|
||||
/**
|
||||
* Indicates an HTTP response code of 304
|
||||
*/
|
||||
- (BOOL)isNotModified;
|
||||
|
||||
/**
|
||||
* Indicates an HTTP response code of 401
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#import "RKNotifications.h"
|
||||
#import "RKNetwork.h"
|
||||
|
||||
extern NSString* cacheResponseCodeKey;
|
||||
extern NSString* cacheMIMETypeKey;
|
||||
extern NSString* cacheURLKey;
|
||||
|
||||
@implementation RKResponse
|
||||
|
||||
@synthesize body = _body, request = _request, failureError = _failureError;
|
||||
@@ -19,6 +23,8 @@
|
||||
if (self) {
|
||||
_body = [[NSMutableData alloc] init];
|
||||
_failureError = nil;
|
||||
_loading = NO;
|
||||
_responseHeaders = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -35,6 +41,18 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithRequest:(RKRequest*)request body:(NSData*)body headers:(NSDictionary*)headers {
|
||||
self = [self initWithRequest:request];
|
||||
if (self) {
|
||||
[_body release];
|
||||
_body = nil;
|
||||
_body = [body retain];
|
||||
_responseHeaders = [headers retain];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithSynchronousRequest:(RKRequest*)request URLResponse:(NSURLResponse*)URLResponse body:(NSData*)body error:(NSError*)error {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -44,6 +62,7 @@
|
||||
_httpURLResponse = [URLResponse retain];
|
||||
_failureError = [error retain];
|
||||
_body = [body retain];
|
||||
_loading = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -51,8 +70,13 @@
|
||||
|
||||
- (void)dealloc {
|
||||
[_httpURLResponse release];
|
||||
_httpURLResponse = nil;
|
||||
[_body release];
|
||||
_body = nil;
|
||||
[_failureError release];
|
||||
_failureError = nil;
|
||||
[_responseHeaders release];
|
||||
_responseHeaders = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -75,6 +99,8 @@
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response {
|
||||
NSLog(@"NSHTTPURLResponse Status Code: %d", [response statusCode]);
|
||||
NSLog(@"Headers: %@", [response allHeaderFields]);
|
||||
_httpURLResponse = [response retain];
|
||||
}
|
||||
|
||||
@@ -104,6 +130,10 @@
|
||||
return [NSHTTPURLResponse localizedStringForStatusCode:[self statusCode]];
|
||||
}
|
||||
|
||||
- (NSData*)body {
|
||||
return _body;
|
||||
}
|
||||
|
||||
- (NSString*)bodyAsString {
|
||||
return [[[NSString alloc] initWithData:self.body encoding:NSUTF8StringEncoding] autorelease];
|
||||
}
|
||||
@@ -123,19 +153,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)wasLoadedFromCache {
|
||||
return (_responseHeaders != nil);
|
||||
}
|
||||
|
||||
- (NSURL*)URL {
|
||||
if ([self wasLoadedFromCache]) {
|
||||
return [NSURL URLWithString:[_responseHeaders valueForKey:cacheURLKey]];
|
||||
}
|
||||
return [_httpURLResponse URL];
|
||||
}
|
||||
|
||||
- (NSString*)MIMEType {
|
||||
if ([self wasLoadedFromCache]) {
|
||||
return [_responseHeaders valueForKey:cacheMIMETypeKey];
|
||||
}
|
||||
return [_httpURLResponse MIMEType];
|
||||
}
|
||||
|
||||
- (NSInteger)statusCode {
|
||||
if ([self wasLoadedFromCache]) {
|
||||
return [[_responseHeaders valueForKey:cacheResponseCodeKey] intValue];
|
||||
}
|
||||
return [_httpURLResponse statusCode];
|
||||
}
|
||||
|
||||
- (NSDictionary*)allHeaderFields {
|
||||
if ([self wasLoadedFromCache]) {
|
||||
return _responseHeaders;
|
||||
}
|
||||
return [_httpURLResponse allHeaderFields];
|
||||
}
|
||||
|
||||
@@ -156,7 +202,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)isSuccessful {
|
||||
return ([self statusCode] >= 200 && [self statusCode] < 300);
|
||||
return (([self statusCode] >= 200 && [self statusCode] < 300) || ([self wasLoadedFromCache]));
|
||||
}
|
||||
|
||||
- (BOOL)isRedirection {
|
||||
@@ -183,6 +229,10 @@
|
||||
return ([self statusCode] == 201);
|
||||
}
|
||||
|
||||
- (BOOL)isNotModified {
|
||||
return ([self statusCode] == 304);
|
||||
}
|
||||
|
||||
- (BOOL)isUnauthorized {
|
||||
return ([self statusCode] == 401);
|
||||
}
|
||||
@@ -225,23 +275,30 @@
|
||||
|
||||
- (BOOL)isHTML {
|
||||
NSString* contentType = [self contentType];
|
||||
return contentType && ([contentType rangeOfString:@"text/html" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0 ||
|
||||
[self isXHTML]);
|
||||
return (contentType && ([contentType rangeOfString:@"text/html"
|
||||
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0 ||
|
||||
[self isXHTML]));
|
||||
}
|
||||
|
||||
- (BOOL)isXHTML {
|
||||
NSString* contentType = [self contentType];
|
||||
return contentType && [contentType rangeOfString:@"application/xhtml+xml" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0;
|
||||
return (contentType &&
|
||||
[contentType rangeOfString:@"application/xhtml+xml"
|
||||
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isXML {
|
||||
NSString* contentType = [self contentType];
|
||||
return contentType && [contentType rangeOfString:@"application/xml" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0;
|
||||
return (contentType &&
|
||||
[contentType rangeOfString:@"application/xml"
|
||||
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isJSON {
|
||||
NSString* contentType = [self contentType];
|
||||
return contentType && [contentType rangeOfString:@"application/json" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0;
|
||||
return (contentType &&
|
||||
[contentType rangeOfString:@"application/json"
|
||||
options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
_objectManager = objectManager;
|
||||
[self.objectManager.client setupRequest:self];
|
||||
}
|
||||
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,15 @@
|
||||
mapper.delegate = self;
|
||||
RKObjectMappingResult* result = [mapper performMapping];
|
||||
|
||||
if (nil == result && RKRequestMethodDELETE == self.method && [mapper.errors count] == 1) {
|
||||
NSError* error = [mapper.errors objectAtIndex:0];
|
||||
if (error.domain == RKRestKitErrorDomain && error.code == RKObjectMapperErrorUnmappableContent) {
|
||||
// If this is a delete request, and the error is an "unmappable content" error, return an empty result
|
||||
// because delete requests should allow for no objects to come back in the response (you just deleted the object).
|
||||
result = [[[RKObjectMappingResult alloc] initWithDictionary:[NSDictionary dictionary]] autorelease];
|
||||
}
|
||||
}
|
||||
|
||||
if (nil == result) {
|
||||
// TODO: Logging macros
|
||||
NSLog(@"GOT MAPPING ERRORS: %@", mapper.errors);
|
||||
@@ -239,13 +248,19 @@
|
||||
- (void)didFailLoadWithError:(NSError*)error {
|
||||
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
|
||||
[_delegate request:self didFailLoadWithError:error];
|
||||
}
|
||||
|
||||
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
|
||||
|
||||
[self finalizeLoad:NO];
|
||||
if (_cachePolicy & RKRequestCachePolicyLoadOnError &&
|
||||
[[[RKClient sharedClient] cache] hasResponseForRequest:self]) {
|
||||
|
||||
[self didFinishLoad:[[[RKClient sharedClient] cache] responseForRequest:self]];
|
||||
} else {
|
||||
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
|
||||
[_delegate request:self didFailLoadWithError:error];
|
||||
}
|
||||
|
||||
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
|
||||
|
||||
[self finalizeLoad:NO];
|
||||
}
|
||||
|
||||
[pool release];
|
||||
}
|
||||
@@ -253,7 +268,17 @@
|
||||
// NOTE: We do NOT call super here. We are overloading the default behavior from RKRequest
|
||||
- (void)didFinishLoad:(RKResponse*)response {
|
||||
_response = [response retain];
|
||||
|
||||
|
||||
if ((_cachePolicy & RKRequestCachePolicyEtag) && [response isNotModified]) {
|
||||
[_response release];
|
||||
_response = nil;
|
||||
_response = [[[[RKClient sharedClient] cache] responseForRequest:self] retain];
|
||||
}
|
||||
|
||||
if (![_response wasLoadedFromCache] && [_response isSuccessful] && (_cachePolicy != RKRequestCachePolicyNone)) {
|
||||
[[[RKClient sharedClient] cache] storeResponse:_response forRequest:self];
|
||||
}
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
|
||||
[_delegate request:self didLoadResponse:response];
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
[self addErrorWithCode:RKObjectMapperErrorObjectMappingTypeMismatch message:errorMessage keyPath:keyPath userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (id mappableObject in mappableObjects) {
|
||||
id destinationObject = [self objectWithMapping:mapping andData:mappableObject];
|
||||
BOOL success = [self mapFromObject:mappableObject toObject:destinationObject atKeyPath:keyPath usingMapping:mapping];
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
//
|
||||
// RKRequestFilterableTTModel.h
|
||||
// RKFilterableObjectLoaderTTModel.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 2/12/10.
|
||||
// Copyright 2010 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestTTModel.h"
|
||||
#import "RKObjectLoaderTTModel.h"
|
||||
#import "../Support/RKSearchEngine.h"
|
||||
|
||||
/**
|
||||
* Provides an interface for searching and filtering a collection
|
||||
* of objects loaded from a remote source
|
||||
*/
|
||||
@interface RKRequestFilterableTTModel : RKRequestTTModel {
|
||||
@interface RKFilterableObjectLoaderTTModel : RKObjectLoaderTTModel {
|
||||
RKSearchEngine* _searchEngine;
|
||||
NSPredicate* _predicate;
|
||||
NSArray* _sortDescriptors;
|
||||
@@ -1,14 +1,14 @@
|
||||
//
|
||||
// RKRequestFilterableTTModel.m
|
||||
// RKFilterableObjectLoaderTTModel.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 2/12/10.
|
||||
// Copyright 2010 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestFilterableTTModel.h"
|
||||
#import "RKFilterableObjectLoaderTTModel.h"
|
||||
|
||||
@implementation RKRequestFilterableTTModel
|
||||
@implementation RKFilterableObjectLoaderTTModel
|
||||
|
||||
@synthesize searchEngine = _searchEngine;
|
||||
@synthesize predicate = _predicate;
|
||||
20
Code/Three20/RKMappableObjectTableItem.h
Normal file
20
Code/Three20/RKMappableObjectTableItem.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// RKMappableObjectTableItem.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 4/26/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Three20/Three20.h>
|
||||
#import "../RestKit.h"
|
||||
|
||||
@interface RKMappableObjectTableItem : TTTableLinkedItem {
|
||||
NSObject<RKObjectMappable>* _object;
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSObject<RKObjectMappable>* object;
|
||||
|
||||
+ (id)itemWithObject:(NSObject<RKObjectMappable>*)object;
|
||||
|
||||
@end
|
||||
21
Code/Three20/RKMappableObjectTableItem.m
Normal file
21
Code/Three20/RKMappableObjectTableItem.m
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// RKMappableObjectTableItem.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 4/26/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKMappableObjectTableItem.h"
|
||||
|
||||
@implementation RKMappableObjectTableItem
|
||||
|
||||
@synthesize object = _object;
|
||||
|
||||
+ (id)itemWithObject:(NSObject<RKObjectMappable>*)object {
|
||||
RKMappableObjectTableItem* tableItem = [self new];
|
||||
tableItem.object = object;
|
||||
return [tableItem autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// RKRequestTTModel.h
|
||||
// RKObjectLoaderTTModel.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 2/9/10.
|
||||
@@ -10,64 +10,23 @@
|
||||
#import "../RestKit.h"
|
||||
|
||||
/**
|
||||
* Generic class for loading a remote model using a RestKit request and supplying the model to a
|
||||
* Generic class for loading a remote model using a RestKit object loader and supplying the model to a
|
||||
* TTListDataSource subclass
|
||||
*/
|
||||
@interface RKRequestTTModel : TTModel <RKObjectLoaderDelegate> {
|
||||
@interface RKObjectLoaderTTModel : TTModel <RKObjectLoaderDelegate> {
|
||||
NSArray *_objects;
|
||||
BOOL _isLoaded;
|
||||
BOOL _isLoading;
|
||||
BOOL _cacheLoaded;
|
||||
BOOL _emptyReloadAttempted;
|
||||
|
||||
NSString* _resourcePath;
|
||||
NSDictionary* _params;
|
||||
RKRequestMethod _method;
|
||||
Class _objectClass;
|
||||
NSString* _keyPath;
|
||||
RKObjectLoader* _objectLoader;
|
||||
|
||||
NSTimeInterval _refreshRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain objects loaded via this model
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* objects;
|
||||
|
||||
/**
|
||||
* The resourcePath used to create this model
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString* resourcePath;
|
||||
|
||||
/**
|
||||
* The NSDate object representing the last time this model was loaded.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSDate* loadedTime;
|
||||
|
||||
/**
|
||||
* Request parameters
|
||||
*/
|
||||
@property (nonatomic, retain) NSDictionary* params;
|
||||
|
||||
/**
|
||||
* The HTTP method to load the models with. Defaults to RKRequestMethodGET
|
||||
*/
|
||||
@property (nonatomic, assign) RKRequestMethod method;
|
||||
|
||||
/**
|
||||
* The rate at which this model should be refreshed after initial load.
|
||||
* Defaults to the value returned by + (NSTimeInterval)defaultRefreshRate.
|
||||
*/
|
||||
@property (assign) NSTimeInterval refreshRate;
|
||||
|
||||
/**
|
||||
* Init methods for creating new models
|
||||
*/
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath;
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params;
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass;
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass keyPath:(NSString*)keyPath;
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method;
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/// @name Global Configuration
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The NSDate representing the first time the app was run. This defaultLoadedTime
|
||||
@@ -90,4 +49,44 @@
|
||||
*/
|
||||
+ (void)setDefaultRefreshRate:(NSTimeInterval)newDefaultRefreshRate;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/// @name Accessing Model Data
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Domain objects loaded via this model
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* objects;
|
||||
|
||||
/**
|
||||
* The object loader responsible for loading the data accessed via this model
|
||||
*/
|
||||
@property (nonatomic, readonly) RKObjectLoader* objectLoader;
|
||||
|
||||
/**
|
||||
* The NSDate object representing the last time this model was loaded.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSDate* loadedTime;
|
||||
|
||||
/**
|
||||
* The rate at which this model should be refreshed after initial load.
|
||||
* Defaults to the value returned by + (NSTimeInterval)defaultRefreshRate.
|
||||
*/
|
||||
@property (assign) NSTimeInterval refreshRate;
|
||||
|
||||
+ (RKObjectLoaderTTModel*)modelWithObjectLoader:(RKObjectLoader*)objectLoader;
|
||||
- (id)initWithObjectLoader:(RKObjectLoader*)objectLoader;
|
||||
|
||||
/**
|
||||
* Load the model
|
||||
*/
|
||||
- (void)load;
|
||||
|
||||
//// TODO: Does this work?
|
||||
//// Loads a new object loader
|
||||
//- (void)changeModelWithObjectLoader:(RKObjectLoader*)objectLoader;
|
||||
// [model didChange]
|
||||
//
|
||||
//- (void)filterWithPredicate:(NSPredicate*)predicate sortDescriptors:(NSArray*)sortDescriptors;
|
||||
//- (void)searchWithText:(NSString*)text predicate: sortDescriptors:
|
||||
@end
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// RKRequestTTModel.m
|
||||
// RKObjectLoaderTTModel.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 2/9/10.
|
||||
// Copyright 2010 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestTTModel.h"
|
||||
#import "RKObjectLoaderTTModel.h"
|
||||
#import "RKManagedObjectStore.h"
|
||||
#import "NSManagedObject+ActiveRecord.h"
|
||||
#import "../Network/Network.h"
|
||||
@@ -15,25 +15,22 @@
|
||||
static NSTimeInterval defaultRefreshRate = NSTimeIntervalSince1970;
|
||||
static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTimeKey";
|
||||
|
||||
@interface RKRequestTTModel (Private)
|
||||
@interface RKObjectLoaderTTModel (Private)
|
||||
|
||||
@property (nonatomic, readonly) NSString* resourcePath;
|
||||
|
||||
- (void)clearLoadedTime;
|
||||
- (void)saveLoadedTime;
|
||||
- (BOOL)errorWarrantsOptionToGoOffline:(NSError*)error;
|
||||
- (void)showAlertWithOptionToGoOfflineForError:(NSError*)error;
|
||||
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex;
|
||||
- (void)modelsDidLoad:(NSArray*)models;
|
||||
- (void)load;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation RKRequestTTModel
|
||||
@implementation RKObjectLoaderTTModel
|
||||
|
||||
@synthesize objects = _objects;
|
||||
@synthesize resourcePath = _resourcePath;
|
||||
@synthesize params = _params;
|
||||
@synthesize method = _method;
|
||||
@synthesize objectLoader = _objectLoader;
|
||||
@synthesize refreshRate = _refreshRate;
|
||||
|
||||
+ (NSDate*)defaultLoadedTime {
|
||||
@@ -54,44 +51,25 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
defaultRefreshRate = newDefaultRefreshRate;
|
||||
}
|
||||
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath {
|
||||
+ (RKObjectLoaderTTModel*)modelWithObjectLoader:(RKObjectLoader*)objectLoader {
|
||||
return [[[self alloc] initWithObjectLoader:objectLoader] autorelease];
|
||||
}
|
||||
|
||||
- (id)initWithObjectLoader:(RKObjectLoader*)objectLoader {
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_resourcePath = [resourcePath retain];
|
||||
}
|
||||
return self;
|
||||
if (self) {
|
||||
// TODO: When allowing mutation of object loader, be sure to update...
|
||||
_objectLoader = [objectLoader retain];
|
||||
_objectLoader.delegate = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params {
|
||||
self = [self initWithResourcePath:resourcePath];
|
||||
if (self) {
|
||||
self.params = [params retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass {
|
||||
self = [self initWithResourcePath:resourcePath params:params];
|
||||
if (self) {
|
||||
_objectClass = [klass retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass keyPath:(NSString*)keyPath {
|
||||
self = [self initWithResourcePath:resourcePath params:params objectClass:klass];
|
||||
if (self) {
|
||||
_keyPath = [keyPath retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method {
|
||||
self = [self initWithResourcePath:resourcePath params:params];
|
||||
if (self) {
|
||||
_method = method;
|
||||
}
|
||||
return self;
|
||||
// TODO: Does this work?
|
||||
// Loads a new object loader
|
||||
- (void)updateModelWithObjectLoader:(RKObjectLoader*)objectLoader {
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -100,14 +78,11 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.method = RKRequestMethodGET;
|
||||
self.refreshRate = [RKRequestTTModel defaultRefreshRate];
|
||||
self.params = nil;
|
||||
self.refreshRate = [RKObjectLoaderTTModel defaultRefreshRate];
|
||||
_cacheLoaded = NO;
|
||||
_objects = nil;
|
||||
_isLoaded = NO;
|
||||
_isLoading = NO;
|
||||
_resourcePath = nil;
|
||||
_emptyReloadAttempted = NO;
|
||||
}
|
||||
return self;
|
||||
@@ -117,19 +92,19 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
[[RKRequestQueue sharedQueue] cancelRequestsWithDelegate:self];
|
||||
[_objects release];
|
||||
_objects = nil;
|
||||
[_resourcePath release];
|
||||
_resourcePath = nil;
|
||||
[_objectClass release];
|
||||
_objectClass = nil;
|
||||
[_keyPath release];
|
||||
_keyPath = nil;
|
||||
self.params = nil;
|
||||
[_objectLoader release];
|
||||
_objectLoader = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TTModel
|
||||
|
||||
- (NSString*)resourcePath {
|
||||
return self.objectLoader.resourcePath;
|
||||
}
|
||||
|
||||
- (BOOL)isLoaded {
|
||||
return _isLoaded;
|
||||
}
|
||||
@@ -146,7 +121,11 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
NSTimeInterval sinceNow = [self.loadedTime timeIntervalSinceNow];
|
||||
if (![self isLoading] && !_emptyReloadAttempted && _objects && [_objects count] == 0) {
|
||||
_emptyReloadAttempted = YES;
|
||||
return YES;
|
||||
|
||||
// TODO: Returning YES from here causes the view to enter an infinite
|
||||
// loading state if you switch data sources
|
||||
// return YES;
|
||||
return NO;
|
||||
}
|
||||
return (![self isLoading] && (-sinceNow > _refreshRate));
|
||||
}
|
||||
@@ -166,9 +145,9 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
}
|
||||
|
||||
- (NSDate*)loadedTime {
|
||||
NSDate* loadedTime = [[NSUserDefaults standardUserDefaults] objectForKey:_resourcePath];
|
||||
NSDate* loadedTime = [[NSUserDefaults standardUserDefaults] objectForKey:self.resourcePath];
|
||||
if (loadedTime == nil) {
|
||||
return [RKRequestTTModel defaultLoadedTime];
|
||||
return [RKObjectLoaderTTModel defaultLoadedTime];
|
||||
}
|
||||
return loadedTime;
|
||||
}
|
||||
@@ -186,9 +165,6 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
|
||||
_isLoading = NO;
|
||||
[self didFailLoadWithError:error];
|
||||
// if ([self errorWarrantsOptionToGoOffline:error]) {
|
||||
// [self showAlertWithOptionToGoOfflineForError:error];
|
||||
// }
|
||||
}
|
||||
|
||||
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader*)objectLoader {
|
||||
@@ -199,41 +175,15 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
[self didFailLoadWithError:error];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark RKRequestTTModel (Private)
|
||||
|
||||
// TODO: Can we push this load time into RKRequestCache???
|
||||
- (void)clearLoadedTime {
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:_resourcePath];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:self.resourcePath];
|
||||
}
|
||||
|
||||
- (void)saveLoadedTime {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:_resourcePath];
|
||||
}
|
||||
|
||||
- (BOOL)errorWarrantsOptionToGoOffline:(NSError*)error {
|
||||
switch ([error code]) {
|
||||
case NSURLErrorTimedOut:
|
||||
case NSURLErrorCannotFindHost:
|
||||
case NSURLErrorCannotConnectToHost:
|
||||
case NSURLErrorNetworkConnectionLost:
|
||||
case NSURLErrorDNSLookupFailed:
|
||||
case NSURLErrorNotConnectedToInternet:
|
||||
case NSURLErrorInternationalRoamingOff:
|
||||
return YES;
|
||||
break;
|
||||
default:
|
||||
return NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showAlertWithOptionToGoOfflineForError:(NSError*)error {
|
||||
UIAlertView* alert = [[[UIAlertView alloc] initWithTitle:TTLocalizedString(@"Network Error", @"")
|
||||
message:[error localizedDescription]
|
||||
delegate:nil
|
||||
cancelButtonTitle:TTLocalizedString(@"OK", @"")
|
||||
otherButtonTitles:nil] autorelease];
|
||||
[alert show];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.resourcePath];
|
||||
}
|
||||
|
||||
- (void)modelsDidLoad:(NSArray*)models {
|
||||
@@ -251,6 +201,7 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
// public
|
||||
|
||||
- (void)load {
|
||||
Class managedObjectClass = NSClassFromString(@"RKManagedObject");
|
||||
RKManagedObjectStore* store = [RKObjectManager sharedManager].objectStore;
|
||||
NSArray* cacheFetchRequests = nil;
|
||||
if (store.managedObjectCache) {
|
||||
@@ -264,8 +215,8 @@ static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTi
|
||||
|
||||
_isLoading = YES;
|
||||
[self didStartLoad];
|
||||
[objectLoader send];
|
||||
} else if (cacheFetchRequests && !_cacheLoaded) {
|
||||
[self.objectLoader send];
|
||||
} else if (cacheFetchRequests && !_cacheLoaded && managedObjectClass) {
|
||||
_cacheLoaded = YES;
|
||||
[self modelsDidLoad:[NSManagedObject objectsWithFetchRequests:cacheFetchRequests]];
|
||||
}
|
||||
24
Code/Three20/RKTableViewDataSource.h
Normal file
24
Code/Three20/RKTableViewDataSource.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// RKTableViewDataSource.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 4/26/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Three20/Three20.h>
|
||||
#import "../RestKit.h"
|
||||
|
||||
@interface RKTableViewDataSource : TTTableViewDataSource {
|
||||
NSMutableDictionary* _objectToTableCellMappings;
|
||||
}
|
||||
|
||||
// The objects loaded via the RKObjectLoaderTTModel instance...
|
||||
@property (nonatomic, readonly) NSArray* modelObjects;
|
||||
|
||||
+ (id)dataSource;
|
||||
- (void)registerCellClass:(Class)cellCell forObjectClass:(Class)objectClass; // TODO: Better method name??
|
||||
|
||||
// TODO: Delegate?
|
||||
|
||||
@end
|
||||
84
Code/Three20/RKTableViewDataSource.m
Normal file
84
Code/Three20/RKTableViewDataSource.m
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// RKTableViewDataSource.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Blake Watters on 4/26/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKTableViewDataSource.h"
|
||||
#import "RKMappableObjectTableItem.h"
|
||||
#import "RKObjectLoaderTTModel.h"
|
||||
|
||||
@implementation RKTableViewDataSource
|
||||
|
||||
+ (id)dataSource {
|
||||
return [[self new] autorelease];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objectToTableCellMappings = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setModel:(id<TTModel>)model {
|
||||
if (NO == [model isKindOfClass:[RKObjectLoaderTTModel class]]) {
|
||||
[NSException raise:nil format:@"RKTableViewDataSource is designed to work with RestKit TTModel implementations only"];
|
||||
}
|
||||
|
||||
[super setModel:model];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_objectToTableCellMappings release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSArray*)modelObjects {
|
||||
return [(RKObjectLoaderTTModel*)self.model objects];
|
||||
}
|
||||
|
||||
- (void)registerCellClass:(Class)cellCell forObjectClass:(Class)objectClass {
|
||||
[_objectToTableCellMappings setObject:cellCell forKey:objectClass];
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return [self.modelObjects count];
|
||||
}
|
||||
|
||||
- (Class)tableView:(UITableView*)tableView cellClassForObject:(id)object {
|
||||
RKMappableObjectTableItem* tableItem = (RKMappableObjectTableItem*)object;
|
||||
Class cellClass = [_objectToTableCellMappings objectForKey:[tableItem.object class]];
|
||||
NSAssert(cellClass != nil, @"Must have a registered cell class for type");
|
||||
return cellClass;
|
||||
}
|
||||
|
||||
- (id)tableView:(UITableView*)tableView objectForRowAtIndexPath:(NSIndexPath*)indexPath {
|
||||
if (indexPath.row < [self.modelObjects count]) {
|
||||
NSObject<RKObjectMappable>* mappableObject = [self.modelObjects objectAtIndex:indexPath.row];
|
||||
RKMappableObjectTableItem* tableItem = [RKMappableObjectTableItem itemWithObject:mappableObject];
|
||||
return tableItem;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath*)tableView:(UITableView*)tableView indexPathForObject:(id)object {
|
||||
NSUInteger objectIndex = [self.modelObjects indexOfObject:object];
|
||||
if (objectIndex != NSNotFound) {
|
||||
return [NSIndexPath indexPathForRow:objectIndex inSection:0];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString*)titleForEmpty {
|
||||
return @"Empty!";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,5 +6,7 @@
|
||||
// Copyright 2010 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestTTModel.h"
|
||||
#import "RKRequestFilterableTTModel.h"
|
||||
#import "RKObjectLoaderTTModel.h"
|
||||
#import "RKFilterableObjectLoaderTTModel.h"
|
||||
#import "RKMappableObjectTableItem.h"
|
||||
#import "RKTableViewDataSource.h"
|
||||
|
||||
@@ -79,10 +79,6 @@
|
||||
253A09261255258500976E89 /* RKManagedObjectStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A086312551D8D00976E89 /* RKManagedObjectStore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253A09271255258600976E89 /* RKManagedObjectStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 253A086412551D8D00976E89 /* RKManagedObjectStore.m */; };
|
||||
253A092D125525EE00976E89 /* Three20.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A08A512551D8D00976E89 /* Three20.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253A092E125525EF00976E89 /* RKRequestTTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 253A08A412551D8D00976E89 /* RKRequestTTModel.m */; };
|
||||
253A092F125525F000976E89 /* RKRequestTTModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A08A312551D8D00976E89 /* RKRequestTTModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253A0932125525F100976E89 /* RKRequestFilterableTTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 253A08A012551D8D00976E89 /* RKRequestFilterableTTModel.m */; };
|
||||
253A0933125525F100976E89 /* RKRequestFilterableTTModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A089F12551D8D00976E89 /* RKRequestFilterableTTModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253A09E612552B5300976E89 /* ObjectMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A09E512552B5300976E89 /* ObjectMapping.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253A09F612552BDC00976E89 /* Support.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A09F512552BDC00976E89 /* Support.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
253E04C513798D72005D2E15 /* RKErrorMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 253E04C313798D72005D2E15 /* RKErrorMessage.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -234,6 +230,12 @@
|
||||
3F032AAB10FFBC1F00F35142 /* RKResident.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F032AAA10FFBC1F00F35142 /* RKResident.m */; };
|
||||
3F1912A712DF6B4800C077AD /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F19129712DF6B4800C077AD /* CFNetwork.framework */; };
|
||||
3F1912AF12DF6B6200C077AD /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25E075981279D9AB00B22EC9 /* MobileCoreServices.framework */; };
|
||||
3F278A40139D2AFF009AC3FA /* NSData+MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F278A3A139D2AFF009AC3FA /* NSData+MD5.h */; };
|
||||
3F278A41139D2AFF009AC3FA /* NSData+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F278A3B139D2AFF009AC3FA /* NSData+MD5.m */; };
|
||||
3F278A42139D2AFF009AC3FA /* NSString+MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F278A3C139D2AFF009AC3FA /* NSString+MD5.h */; };
|
||||
3F278A43139D2AFF009AC3FA /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F278A3D139D2AFF009AC3FA /* NSString+MD5.m */; };
|
||||
3F278A44139D2AFF009AC3FA /* RKRequestCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F278A3E139D2AFF009AC3FA /* RKRequestCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F278A45139D2AFF009AC3FA /* RKRequestCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F278A3F139D2AFF009AC3FA /* RKRequestCache.m */; };
|
||||
3F4EAF58134205CF00F944E4 /* RKXMLParserSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F4EAF57134205CF00F944E4 /* RKXMLParserSpec.m */; };
|
||||
3F4EAF5B1342071B00F944E4 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F4EAF5A1342071B00F944E4 /* libxml2.dylib */; };
|
||||
3F5356ED13785DA300132100 /* RKObjectSerializerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F5356EC13785DA300132100 /* RKObjectSerializerSpec.m */; };
|
||||
@@ -241,6 +243,14 @@
|
||||
3F6C3A9610FE7524008F47C5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F6C3A9510FE7524008F47C5 /* UIKit.framework */; };
|
||||
3F71ED3413748536006281CA /* RKObjectMappingProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F71ED3213748536006281CA /* RKObjectMappingProvider.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F71ED3513748536006281CA /* RKObjectMappingProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F71ED3313748536006281CA /* RKObjectMappingProvider.m */; };
|
||||
3F741A5B139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A53139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F741A5C139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A54139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m */; };
|
||||
3F741A5D139D74F1002E7304 /* RKMappableObjectTableItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A55139D74F1002E7304 /* RKMappableObjectTableItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F741A5E139D74F1002E7304 /* RKMappableObjectTableItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */; };
|
||||
3F741A5F139D74F1002E7304 /* RKObjectLoaderTTModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F741A60139D74F1002E7304 /* RKObjectLoaderTTModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */; };
|
||||
3F741A61139D74F1002E7304 /* RKTableViewDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3F741A62139D74F1002E7304 /* RKTableViewDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */; };
|
||||
3FD12C851379AD64008B996A /* RKRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD12C841379AD64008B996A /* RKRouter.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
73057FD61331AD5E001908EE /* JSONKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 73057FC71331AA3D001908EE /* JSONKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
73057FD71331AD67001908EE /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 73057FC81331AA3D001908EE /* JSONKit.m */; };
|
||||
@@ -418,10 +428,6 @@
|
||||
253A089B12551D8D00976E89 /* RestKit_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RestKit_Prefix.pch; sourceTree = "<group>"; };
|
||||
253A089C12551D8D00976E89 /* RKSearchEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKSearchEngine.h; sourceTree = "<group>"; };
|
||||
253A089D12551D8D00976E89 /* RKSearchEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKSearchEngine.m; sourceTree = "<group>"; };
|
||||
253A089F12551D8D00976E89 /* RKRequestFilterableTTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKRequestFilterableTTModel.h; sourceTree = "<group>"; };
|
||||
253A08A012551D8D00976E89 /* RKRequestFilterableTTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRequestFilterableTTModel.m; sourceTree = "<group>"; };
|
||||
253A08A312551D8D00976E89 /* RKRequestTTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKRequestTTModel.h; sourceTree = "<group>"; };
|
||||
253A08A412551D8D00976E89 /* RKRequestTTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRequestTTModel.m; sourceTree = "<group>"; };
|
||||
253A08A512551D8D00976E89 /* Three20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Three20.h; sourceTree = "<group>"; };
|
||||
253A08AE12551EA500976E89 /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Network.h; sourceTree = "<group>"; };
|
||||
253A08B61255212300976E89 /* RKJSONParserSBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKJSONParserSBJSON.m; sourceTree = "<group>"; };
|
||||
@@ -603,6 +609,12 @@
|
||||
3F032AAA10FFBC1F00F35142 /* RKResident.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKResident.m; sourceTree = "<group>"; };
|
||||
3F19126812DF6B3200C077AD /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
3F19129712DF6B4800C077AD /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
|
||||
3F278A3A139D2AFF009AC3FA /* NSData+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+MD5.h"; path = "Code/Network/NSData+MD5.h"; sourceTree = SOURCE_ROOT; };
|
||||
3F278A3B139D2AFF009AC3FA /* NSData+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+MD5.m"; path = "Code/Network/NSData+MD5.m"; sourceTree = SOURCE_ROOT; };
|
||||
3F278A3C139D2AFF009AC3FA /* NSString+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSString+MD5.h"; path = "Code/Network/NSString+MD5.h"; sourceTree = SOURCE_ROOT; };
|
||||
3F278A3D139D2AFF009AC3FA /* NSString+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+MD5.m"; path = "Code/Network/NSString+MD5.m"; sourceTree = SOURCE_ROOT; };
|
||||
3F278A3E139D2AFF009AC3FA /* RKRequestCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRequestCache.h; path = Code/Network/RKRequestCache.h; sourceTree = SOURCE_ROOT; };
|
||||
3F278A3F139D2AFF009AC3FA /* RKRequestCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKRequestCache.m; path = Code/Network/RKRequestCache.m; sourceTree = SOURCE_ROOT; };
|
||||
3F4EAF57134205CF00F944E4 /* RKXMLParserSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKXMLParserSpec.m; sourceTree = "<group>"; };
|
||||
3F4EAF5A1342071B00F944E4 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
|
||||
3F5356EC13785DA300132100 /* RKObjectSerializerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectSerializerSpec.m; sourceTree = "<group>"; };
|
||||
@@ -615,6 +627,16 @@
|
||||
3F7C8ECF1379815A00BEBC29 /* RKErrorMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKErrorMessage.h; sourceTree = "<group>"; };
|
||||
3F7C8ED01379815A00BEBC29 /* RKErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKErrorMessage.m; sourceTree = "<group>"; };
|
||||
73057FC41331A8F6001908EE /* RKJSONParserJSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RKJSONParser+JSONKit.m"; sourceTree = "<group>"; };
|
||||
3F741A53139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKFilterableObjectLoaderTTModel.h; path = ../../../../../RestKit/Code/Three20/RKFilterableObjectLoaderTTModel.h; sourceTree = "<group>"; };
|
||||
3F741A54139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKFilterableObjectLoaderTTModel.m; path = ../../../../../RestKit/Code/Three20/RKFilterableObjectLoaderTTModel.m; sourceTree = "<group>"; };
|
||||
3F741A55139D74F1002E7304 /* RKMappableObjectTableItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKMappableObjectTableItem.h; path = ../../../../../RestKit/Code/Three20/RKMappableObjectTableItem.h; sourceTree = "<group>"; };
|
||||
3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKMappableObjectTableItem.m; path = ../../../../../RestKit/Code/Three20/RKMappableObjectTableItem.m; sourceTree = "<group>"; };
|
||||
3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKObjectLoaderTTModel.h; path = ../../../../../RestKit/Code/Three20/RKObjectLoaderTTModel.h; sourceTree = "<group>"; };
|
||||
3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKObjectLoaderTTModel.m; path = ../../../../../RestKit/Code/Three20/RKObjectLoaderTTModel.m; sourceTree = "<group>"; };
|
||||
3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKTableViewDataSource.h; path = ../../../../../RestKit/Code/Three20/RKTableViewDataSource.h; sourceTree = "<group>"; };
|
||||
3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RKTableViewDataSource.m; path = ../../../../../RestKit/Code/Three20/RKTableViewDataSource.m; sourceTree = "<group>"; };
|
||||
3FD12C841379AD64008B996A /* RKRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRouter.h; path = Code/ObjectMapping/RKRouter.h; sourceTree = SOURCE_ROOT; };
|
||||
73057FC41331A8F6001908EE /* RKJSONParserJSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKJSONParserJSONKit.m; sourceTree = "<group>"; };
|
||||
73057FC71331AA3D001908EE /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JSONKit.h; path = Vendor/JSONKit/JSONKit.h; sourceTree = "<group>"; };
|
||||
73057FC81331AA3D001908EE /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JSONKit.m; path = Vendor/JSONKit/JSONKit.m; sourceTree = "<group>"; };
|
||||
73057FD11331AD2E001908EE /* libRestKitJSONParserJSONKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitJSONParserJSONKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -830,6 +852,12 @@
|
||||
253A086512551D8D00976E89 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3F278A3A139D2AFF009AC3FA /* NSData+MD5.h */,
|
||||
3F278A3B139D2AFF009AC3FA /* NSData+MD5.m */,
|
||||
3F278A3C139D2AFF009AC3FA /* NSString+MD5.h */,
|
||||
3F278A3D139D2AFF009AC3FA /* NSString+MD5.m */,
|
||||
3F278A3E139D2AFF009AC3FA /* RKRequestCache.h */,
|
||||
3F278A3F139D2AFF009AC3FA /* RKRequestCache.m */,
|
||||
253A08AE12551EA500976E89 /* Network.h */,
|
||||
253A086612551D8D00976E89 /* NSDictionary+RKRequestSerialization.h */,
|
||||
253A086712551D8D00976E89 /* NSDictionary+RKRequestSerialization.m */,
|
||||
@@ -886,6 +914,7 @@
|
||||
25F5182413724866009B2E22 /* RKObjectRelationshipMapping.m */,
|
||||
259C64BD137477D0001FE1C5 /* RKObjectMapper.h */,
|
||||
259C64BE137477D0001FE1C5 /* RKObjectMapper.m */,
|
||||
2530078D137838D30074F3FD /* RKObjectMapper_Private.h */,
|
||||
3F71ED3213748536006281CA /* RKObjectMappingProvider.h */,
|
||||
3F71ED3313748536006281CA /* RKObjectMappingProvider.m */,
|
||||
257D2D6E13759D6F008E9649 /* RKObjectMappingResult.h */,
|
||||
@@ -930,11 +959,15 @@
|
||||
253A089E12551D8D00976E89 /* Three20 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3F741A53139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h */,
|
||||
3F741A54139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m */,
|
||||
3F741A55139D74F1002E7304 /* RKMappableObjectTableItem.h */,
|
||||
3F741A56139D74F1002E7304 /* RKMappableObjectTableItem.m */,
|
||||
3F741A57139D74F1002E7304 /* RKObjectLoaderTTModel.h */,
|
||||
3F741A58139D74F1002E7304 /* RKObjectLoaderTTModel.m */,
|
||||
3F741A59139D74F1002E7304 /* RKTableViewDataSource.h */,
|
||||
3F741A5A139D74F1002E7304 /* RKTableViewDataSource.m */,
|
||||
253A08A512551D8D00976E89 /* Three20.h */,
|
||||
253A089F12551D8D00976E89 /* RKRequestFilterableTTModel.h */,
|
||||
253A08A012551D8D00976E89 /* RKRequestFilterableTTModel.m */,
|
||||
253A08A312551D8D00976E89 /* RKRequestTTModel.h */,
|
||||
253A08A412551D8D00976E89 /* RKRequestTTModel.m */,
|
||||
);
|
||||
path = Three20;
|
||||
sourceTree = "<group>";
|
||||
@@ -1308,8 +1341,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
253A092D125525EE00976E89 /* Three20.h in Headers */,
|
||||
253A092F125525F000976E89 /* RKRequestTTModel.h in Headers */,
|
||||
253A0933125525F100976E89 /* RKRequestFilterableTTModel.h in Headers */,
|
||||
3F741A5B139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.h in Headers */,
|
||||
3F741A5D139D74F1002E7304 /* RKMappableObjectTableItem.h in Headers */,
|
||||
3F741A5F139D74F1002E7304 /* RKObjectLoaderTTModel.h in Headers */,
|
||||
3F741A61139D74F1002E7304 /* RKTableViewDataSource.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1330,8 +1365,11 @@
|
||||
73FE56C7126CB91600E0F30B /* RKURL.h in Headers */,
|
||||
2538C05C12A6C44A0006903C /* RKRequestQueue.h in Headers */,
|
||||
250C29FD134185D2000A3551 /* RKNetwork.h in Headers */,
|
||||
3F278A44139D2AFF009AC3FA /* RKRequestCache.h in Headers */,
|
||||
25A1CB3F138404CF00A7D5C9 /* RKRequestSerialization.h in Headers */,
|
||||
257FB7061395DABB003A628E /* RKRequest_Internals.h in Headers */,
|
||||
3F278A40139D2AFF009AC3FA /* NSData+MD5.h in Headers */,
|
||||
3F278A42139D2AFF009AC3FA /* NSString+MD5.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1805,8 +1843,10 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
253A092E125525EF00976E89 /* RKRequestTTModel.m in Sources */,
|
||||
253A0932125525F100976E89 /* RKRequestFilterableTTModel.m in Sources */,
|
||||
3F741A5C139D74F1002E7304 /* RKFilterableObjectLoaderTTModel.m in Sources */,
|
||||
3F741A5E139D74F1002E7304 /* RKMappableObjectTableItem.m in Sources */,
|
||||
3F741A60139D74F1002E7304 /* RKObjectLoaderTTModel.m in Sources */,
|
||||
3F741A62139D74F1002E7304 /* RKTableViewDataSource.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1826,6 +1866,9 @@
|
||||
2538C05D12A6C44A0006903C /* RKRequestQueue.m in Sources */,
|
||||
250C29FE134185D2000A3551 /* RKNetwork.m in Sources */,
|
||||
25A1CB40138404EB00A7D5C9 /* RKRequestSerialization.m in Sources */,
|
||||
3F278A41139D2AFF009AC3FA /* NSData+MD5.m in Sources */,
|
||||
3F278A43139D2AFF009AC3FA /* NSString+MD5.m in Sources */,
|
||||
3F278A45139D2AFF009AC3FA /* RKRequestCache.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
mapping.primaryKeyAttribute = @"railsID";
|
||||
[mapping addAttributeMapping:[RKObjectAttributeMapping mappingFromKeyPath:@"monkey.id" toKeyPath:@"railsID"]];
|
||||
|
||||
[RKHuman truncateAll];
|
||||
RKHuman* human = [RKHuman object];
|
||||
human.railsID = [NSNumber numberWithInt:123];
|
||||
[store save];
|
||||
|
||||
14
Specs/Network/NSData+MD5.h
Normal file
14
Specs/Network/NSData+MD5.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NSData+MD5.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@interface NSData (MD5)
|
||||
|
||||
- (NSString*)MD5;
|
||||
|
||||
@end
|
||||
30
Specs/Network/NSData+MD5.m
Normal file
30
Specs/Network/NSData+MD5.m
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// NSData+MD5.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+MD5.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation NSData (MD5)
|
||||
|
||||
- (NSString*)MD5 {
|
||||
// Create byte array of unsigned chars
|
||||
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
|
||||
|
||||
// Create 16 byte MD5 hash value, store in buffer
|
||||
CC_MD5(self.bytes, self.length, md5Buffer);
|
||||
|
||||
// Convert unsigned char buffer to NSString of hex values
|
||||
NSMutableString* output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
||||
[output appendFormat:@"%02x",md5Buffer[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@end
|
||||
14
Specs/Network/NSString+MD5.h
Normal file
14
Specs/Network/NSString+MD5.h
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NSString+MD5.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
@interface NSString (MD5)
|
||||
|
||||
- (NSString*)MD5;
|
||||
|
||||
@end
|
||||
33
Specs/Network/NSString+MD5.m
Normal file
33
Specs/Network/NSString+MD5.m
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// NSString+MD5.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+MD5.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
@implementation NSString (MD5)
|
||||
|
||||
- (NSString*)MD5 {
|
||||
// Create pointer to the string as UTF8
|
||||
const char* ptr = [self UTF8String];
|
||||
|
||||
// Create byte array of unsigned chars
|
||||
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
|
||||
|
||||
// Create 16 byte MD5 hash value, store in buffer
|
||||
CC_MD5(ptr, strlen(ptr), md5Buffer);
|
||||
|
||||
// Convert MD5 value in the buffer to NSString of hex values
|
||||
NSMutableString* output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
||||
[output appendFormat:@"%02x",md5Buffer[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,5 +29,18 @@
|
||||
RKReachabilityNetworkStatus status = [client.baseURLReachabilityObserver networkStatus];
|
||||
[expectThat(status) shouldNot:be(RKReachabilityIndeterminate)];
|
||||
}
|
||||
- (void)itShouldSetTheCachePolicyOfTheRequest {
|
||||
RKClient* client = [RKClient clientWithBaseURL:@"http://restkit.org"];
|
||||
client.cachePolicy = RKRequestCachePolicyLoadIfOffline;
|
||||
RKRequest* request = [client requestWithResourcePath:@"" delegate:nil];
|
||||
[expectThat(request.cachePolicy) should:be(RKRequestCachePolicyLoadIfOffline)];
|
||||
}
|
||||
|
||||
- (void)itShouldInitializeTheCacheOfTheRequest {
|
||||
RKClient* client = [RKClient clientWithBaseURL:@"http://restkit.org"];
|
||||
client.cache = [[[RKRequestCache alloc] init] autorelease];
|
||||
RKRequest* request = [client requestWithResourcePath:@"" delegate:nil];
|
||||
[expectThat(request.cache) should:be(client.cache)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
53
Specs/Network/RKRequestCache.h
Normal file
53
Specs/Network/RKRequestCache.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// RKRequestCache.h
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequest.h"
|
||||
#import "RKResponse.h"
|
||||
|
||||
|
||||
/**
|
||||
* Storage policy. Determines if we clear the cache out when the app is shut down.
|
||||
* Cache instance needs to register for
|
||||
*/
|
||||
typedef enum {
|
||||
RKRequestCacheStoragePolicyDisabled, // The cache has been disabled. Attempts to store data will silently fail
|
||||
RKRequestCacheStoragePolicyForDurationOfSession, // Cache data for the length of the session. Clear cache at app exit.
|
||||
RKRequestCacheStoragePolicyPermanently // Cache data permanently, until explicitly expired or flushed
|
||||
} RKRequestCacheStoragePolicy;
|
||||
|
||||
|
||||
@interface RKRequestCache : NSObject {
|
||||
NSString* _cachePath;
|
||||
RKRequestCacheStoragePolicy _storagePolicy;
|
||||
NSRecursiveLock* _cacheLock;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) NSString* cachePath; // Full path to the cache
|
||||
@property (nonatomic, assign) RKRequestCacheStoragePolicy storagePolicy; // User can change storage policy.
|
||||
|
||||
+ (NSDateFormatter*)rfc1123DateFormatter;
|
||||
|
||||
- (id)initWithCachePath:(NSString*)cachePath storagePolicy:(RKRequestCacheStoragePolicy)storagePolicy;
|
||||
|
||||
- (NSString*)pathForRequest:(RKRequest*)request;
|
||||
|
||||
- (BOOL)hasResponseForRequest:(RKRequest*)request;
|
||||
|
||||
- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request;
|
||||
|
||||
//- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request expires:(NSTimeInterval)expirationAge;
|
||||
|
||||
- (RKResponse*)responseForRequest:(RKRequest*)request;
|
||||
|
||||
- (NSString*)etagForRequest:(RKRequest*)request;
|
||||
|
||||
- (void)invalidateRequest:(RKRequest*)request;
|
||||
|
||||
- (void)invalidateAll;
|
||||
|
||||
@end
|
||||
289
Specs/Network/RKRequestCache.m
Normal file
289
Specs/Network/RKRequestCache.m
Normal file
@@ -0,0 +1,289 @@
|
||||
//
|
||||
// RKRequestCache.m
|
||||
// RestKit
|
||||
//
|
||||
// Created by Jeff Arena on 4/4/11.
|
||||
// Copyright 2011 Two Toasters. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RKRequestCache.h"
|
||||
|
||||
|
||||
static NSString* sessionCacheFolder = @"SessionStore";
|
||||
static NSString* permanentCacheFolder = @"PermanentStore";
|
||||
static NSString* headersExtension = @"headers";
|
||||
static NSString* cacheDateHeaderKey = @"X-RESTKIT-CACHEDATE";
|
||||
|
||||
static NSDateFormatter* __rfc1123DateFormatter;
|
||||
|
||||
@implementation RKRequestCache
|
||||
|
||||
@synthesize storagePolicy = _storagePolicy;
|
||||
|
||||
+ (NSDateFormatter*)rfc1123DateFormatter {
|
||||
if (__rfc1123DateFormatter == nil) {
|
||||
__rfc1123DateFormatter = [[NSDateFormatter alloc] init];
|
||||
[__rfc1123DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
[__rfc1123DateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"];
|
||||
}
|
||||
return __rfc1123DateFormatter;
|
||||
}
|
||||
|
||||
- (id)initWithCachePath:(NSString*)cachePath storagePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
if (self = [super init]) {
|
||||
_cachePath = [cachePath copy];
|
||||
_cacheLock = [[NSRecursiveLock alloc] init];
|
||||
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
NSArray* pathArray = [NSArray arrayWithObjects:
|
||||
_cachePath,
|
||||
[_cachePath stringByAppendingPathComponent:sessionCacheFolder],
|
||||
[_cachePath stringByAppendingPathComponent:permanentCacheFolder],
|
||||
nil];
|
||||
|
||||
for (NSString* path in pathArray) {
|
||||
BOOL isDirectory = NO;
|
||||
BOOL fileExists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
|
||||
if (!fileExists) {
|
||||
NSError* error = nil;
|
||||
BOOL created = [fileManager createDirectoryAtPath:path
|
||||
withIntermediateDirectories:NO
|
||||
attributes:nil
|
||||
error:&error];
|
||||
if (!created || error != nil) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to create cache directory at %@: error %@",
|
||||
path, [error localizedDescription]);
|
||||
}
|
||||
} else if (!isDirectory) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to create cache directory: Directory already exists: %@", path);
|
||||
}
|
||||
}
|
||||
|
||||
[fileManager release];
|
||||
|
||||
self.storagePolicy = storagePolicy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_cachePath release];
|
||||
_cachePath = nil;
|
||||
[_cacheLock release];
|
||||
_cacheLock = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString*)cachePath {
|
||||
return _cachePath;
|
||||
}
|
||||
|
||||
- (NSString*)pathForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
NSString* pathForRequest = nil;
|
||||
|
||||
if (_storagePolicy == RKRequestCacheStoragePolicyForDurationOfSession) {
|
||||
pathForRequest = [[_cachePath stringByAppendingPathComponent:sessionCacheFolder]
|
||||
stringByAppendingPathComponent:[request cacheKey]];
|
||||
|
||||
} else if (_storagePolicy == RKRequestCacheStoragePolicyPermanently) {
|
||||
pathForRequest = [[_cachePath stringByAppendingPathComponent:permanentCacheFolder]
|
||||
stringByAppendingPathComponent:[request cacheKey]];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return pathForRequest;
|
||||
}
|
||||
|
||||
- (BOOL)hasResponseForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
BOOL hasEntryForRequest = NO;
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
hasEntryForRequest = ([fileManager fileExistsAtPath:cachePath] &&
|
||||
[fileManager fileExistsAtPath:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]]);
|
||||
|
||||
[fileManager release];
|
||||
|
||||
[_cacheLock unlock];
|
||||
return hasEntryForRequest;
|
||||
}
|
||||
|
||||
- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
if ([self hasResponseForRequest:request]) {
|
||||
[self invalidateRequest:request];
|
||||
}
|
||||
|
||||
if (_storagePolicy != RKRequestCacheStoragePolicyDisabled) {
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSData* body = response.body;
|
||||
if (body) {
|
||||
[body writeToFile:cachePath atomically:NO];
|
||||
}
|
||||
|
||||
NSMutableDictionary* headers = [response.allHeaderFields mutableCopy];
|
||||
NSLog(@"headers = %@", headers);
|
||||
if (headers) {
|
||||
[headers setObject:[[RKRequestCache rfc1123DateFormatter] stringFromDate:[NSDate date]]
|
||||
forKey:cacheDateHeaderKey];
|
||||
[headers writeToFile:[cachePath stringByAppendingPathExtension:headersExtension]
|
||||
atomically:NO];
|
||||
}
|
||||
[headers release];
|
||||
}
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
//- (void)storeResponse:(RKResponse*)response forRequest:(RKRequest*)request expires:(NSTimeInterval)expirationAge {
|
||||
// [_cacheLock lock];
|
||||
//
|
||||
// if (_storagePolicy != RKRequestCacheStoragePolicyDisabled) {
|
||||
// NSString* cachePath = [self pathForRequest:request];
|
||||
// if (cachePath) {
|
||||
// NSData* body = response.body;
|
||||
// if (body) {
|
||||
// [body writeToFile:cachePath atomically:NO];
|
||||
// }
|
||||
//
|
||||
// NSMutableDictionary* headers = [response.allHeaderFields mutableCopy];
|
||||
// if (headers) {
|
||||
// if (expirationAge != 0) {
|
||||
// [headers removeObjectForKey:@"Expires"];
|
||||
// [headers setObject:[NSString stringWithFormat:@"max-age=%i",(int)expirationAge]
|
||||
// forKey:@"Cache-Control"];
|
||||
// }
|
||||
// [headers setObject:[[RKRequestCache rfc1123DateFormatter] stringFromDate:[NSDate date]]
|
||||
// forKey:cacheDateHeaderKey];
|
||||
// [headers writeToFile:[cachePath stringByAppendingPathExtension:headersExtension]
|
||||
// atomically:NO];
|
||||
// }
|
||||
// [headers release];
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// [_cacheLock unlock];
|
||||
//}
|
||||
|
||||
- (RKResponse*)responseForRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
RKResponse* response = nil;
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSData* responseData = [NSData dataWithContentsOfFile:cachePath];
|
||||
|
||||
NSDictionary* responseHeaders = [NSDictionary dictionaryWithContentsOfFile:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]];
|
||||
|
||||
response = [[[RKResponse alloc] initWithRequest:request body:responseData headers:responseHeaders] autorelease];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return response;
|
||||
}
|
||||
|
||||
- (NSString*)etagForRequest:(RKRequest*)request {
|
||||
NSString* etag = nil;
|
||||
[_cacheLock lock];
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSDictionary* responseHeaders = [NSDictionary dictionaryWithContentsOfFile:
|
||||
[cachePath stringByAppendingPathExtension:headersExtension]];
|
||||
if (responseHeaders) {
|
||||
for (NSString* responseHeader in responseHeaders) {
|
||||
if ([responseHeader isEqualToString:@"Etag"]) {
|
||||
etag = [responseHeaders objectForKey:responseHeader];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
return etag;
|
||||
}
|
||||
|
||||
- (void)invalidateRequest:(RKRequest*)request {
|
||||
[_cacheLock lock];
|
||||
|
||||
NSString* cachePath = [self pathForRequest:request];
|
||||
if (cachePath) {
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
[fileManager removeItemAtPath:cachePath error:NULL];
|
||||
[fileManager removeItemAtPath:[cachePath stringByAppendingPathExtension:headersExtension]
|
||||
error:NULL];
|
||||
[fileManager release];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)invalidateWithStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
[_cacheLock lock];
|
||||
|
||||
if (_cachePath && storagePolicy != RKRequestCacheStoragePolicyDisabled) {
|
||||
NSString* cachePath = nil;
|
||||
if (storagePolicy == RKRequestCacheStoragePolicyForDurationOfSession) {
|
||||
cachePath = [_cachePath stringByAppendingPathComponent:sessionCacheFolder];
|
||||
} else {
|
||||
cachePath = [_cachePath stringByAppendingPathComponent:permanentCacheFolder];
|
||||
}
|
||||
|
||||
NSFileManager* fileManager = [[NSFileManager alloc] init];
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
BOOL fileExists = [fileManager fileExistsAtPath:cachePath isDirectory:&isDirectory];
|
||||
|
||||
if (fileExists && isDirectory) {
|
||||
NSError* error = nil;
|
||||
NSArray* cacheEntries = [fileManager contentsOfDirectoryAtPath:cachePath error:&error];
|
||||
|
||||
if (nil == error) {
|
||||
for (NSString* cacheEntry in cacheEntries) {
|
||||
NSString* cacheEntryPath = [cachePath stringByAppendingPathComponent:cacheEntry];
|
||||
[fileManager removeItemAtPath:cacheEntryPath error:&error];
|
||||
[fileManager removeItemAtPath:[cacheEntryPath stringByAppendingPathExtension:headersExtension]
|
||||
error:&error];
|
||||
if (nil != error) {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to delete cache entry for file: %@", cacheEntryPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSLog(@"[RestKit] RKRequestCache: Failed to fetch list of cache entries for cache path: %@", cachePath);
|
||||
}
|
||||
}
|
||||
|
||||
[fileManager release];
|
||||
}
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)invalidateAll {
|
||||
[_cacheLock lock];
|
||||
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyForDurationOfSession];
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyPermanently];
|
||||
|
||||
[_cacheLock unlock];
|
||||
}
|
||||
|
||||
- (void)setStoragePolicy:(RKRequestCacheStoragePolicy)storagePolicy {
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyForDurationOfSession];
|
||||
if (storagePolicy == RKRequestCacheStoragePolicyDisabled) {
|
||||
[self invalidateWithStoragePolicy:RKRequestCacheStoragePolicyPermanently];
|
||||
}
|
||||
_storagePolicy = storagePolicy;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
@interface RKRequest (Private)
|
||||
- (void)fireAsynchronousRequest;
|
||||
- (void)shouldDispatchRequest;
|
||||
@end
|
||||
|
||||
@interface RKRequestSpec : NSObject <UISpec> {
|
||||
@@ -144,4 +145,237 @@
|
||||
[expectThat([loader success]) should:be(YES)];
|
||||
}
|
||||
|
||||
#pragma mark RKRequestCachePolicy Specs
|
||||
|
||||
- (void)itShouldSendTheRequestWhenTheCachePolicyIsNone {
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyNone;
|
||||
request.delegate = loader;
|
||||
[request sendAsynchronously];
|
||||
[loader waitForResponse];
|
||||
[expectThat([loader success]) should:be(YES)];
|
||||
}
|
||||
|
||||
- (void)itShouldCacheTheRequestHeadersAndBodyIncludingOurOwnCustomTimestampHeader {
|
||||
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];
|
||||
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
request.cache = cache;
|
||||
request.delegate = loader;
|
||||
[request sendAsynchronously];
|
||||
[loader waitForResponse];
|
||||
[expectThat([loader success]) should:be(YES)];
|
||||
NSDictionary* headers = [cache headersForRequest:request];
|
||||
[expectThat([headers valueForKey:@"X-RESTKIT-CACHEDATE"]) shouldNot:be(nil)];
|
||||
[expectThat([headers valueForKey:@"Etag"]) should:be(@"686897696a7c876b7e")];
|
||||
[expectThat([[cache responseForRequest:request] bodyAsString]) should:be(@"This Should Get Cached")];
|
||||
}
|
||||
|
||||
- (void)itShouldGenerateAUniqueCacheKeyBasedOnTheUrlTheMethodAndTheHTTPBody {
|
||||
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:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
|
||||
NSString* cacheKeyGET = [request cacheKey];
|
||||
request.method = RKRequestMethodDELETE;
|
||||
// Don't cache delete. cache key should be nil.
|
||||
[expectThat([request cacheKey]) should:be(nil)];
|
||||
|
||||
request.method = RKRequestMethodPOST;
|
||||
[expectThat([request cacheKey]) shouldNot:be(nil)];
|
||||
[expectThat(cacheKeyGET) shouldNot:be([request cacheKey])];
|
||||
request.params = [NSDictionary dictionaryWithObject:@"val" forKey:@"key"];
|
||||
NSString* cacheKeyPOST = [request cacheKey];
|
||||
[expectThat(cacheKeyPOST) shouldNot:be(nil)];
|
||||
request.method = RKRequestMethodPUT;
|
||||
[expectThat(cacheKeyPOST) shouldNot:be([request cacheKey])];
|
||||
[expectThat([request cacheKey]) shouldNot:be(nil)];
|
||||
}
|
||||
|
||||
- (void)itShouldLoadFromCacheWhenWeRecieveA304 {
|
||||
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];
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
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")];
|
||||
[expectThat([cache etagForRequest:request]) should:be(@"686897696a7c876b7e")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
|
||||
}
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
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")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)itShouldLoadFromTheCacheIfThereIsAnError {
|
||||
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];
|
||||
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
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")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
|
||||
}
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyLoadOnError;
|
||||
request.cache = cache;
|
||||
request.delegate = loader;
|
||||
[request didFailLoadWithError:[NSError errorWithDomain:@"Fake" code:0 userInfo:nil]];
|
||||
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)itShouldLoadFromTheCacheIfWeAreOffline {
|
||||
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];
|
||||
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
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")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
|
||||
}
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyLoadIfOffline;
|
||||
request.cache = cache;
|
||||
request.delegate = loader;
|
||||
id mock = [OCMockObject partialMockForObject:request];
|
||||
BOOL returnValue = NO;
|
||||
[[[mock expect] andReturnValue:OCMOCK_VALUE(returnValue)] shouldDispatchRequest];
|
||||
[mock sendAsynchronously];
|
||||
[expectThat([loader.response bodyAsString]) should:be(@"This Should Get Cached")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)itShouldCacheTheStatusCodeMIMETypeAndURL {
|
||||
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];
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
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")];
|
||||
NSLog(@"Headers: %@", [cache headersForRequest:request]);
|
||||
[expectThat([cache etagForRequest:request]) should:be(@"686897696a7c876b7e")];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(NO)];
|
||||
}
|
||||
{
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
NSString* url = [NSString stringWithFormat:@"%@/etags/cached", RKSpecGetBaseURL()];
|
||||
NSURL* URL = [NSURL URLWithString:url];
|
||||
RKRequest* request = [[RKRequest alloc] initWithURL:URL];
|
||||
request.cachePolicy = RKRequestCachePolicyEtag;
|
||||
request.cache = cache;
|
||||
request.delegate = loader;
|
||||
[request sendAsynchronously];
|
||||
[loader waitForResponse];
|
||||
[expectThat([loader success]) should:be(YES)];
|
||||
[expectThat([loader.response wasLoadedFromCache]) should:be(YES)];
|
||||
[expectThat(loader.response.statusCode) should:be(200)];
|
||||
[expectThat(loader.response.MIMEType) should:be(@"text/html")];
|
||||
[expectThat([loader.response.URL absoluteString]) should:be(@"http://localhost:4567/etags/cached")];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
@interface RKObjectManagerSpec : RKSpec {
|
||||
RKObjectManager* _objectManager;
|
||||
RKSpecResponseLoader* _responseLoader;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -67,8 +66,6 @@
|
||||
RKObjectRouter* router = [[[RKObjectRouter alloc] init] autorelease];
|
||||
[router routeClass:[RKHuman class] toResourcePath:@"/humans" forMethod:RKRequestMethodPOST];
|
||||
_objectManager.router = router;
|
||||
|
||||
_responseLoader = [[RKSpecResponseLoader alloc] init];
|
||||
}
|
||||
|
||||
- (void)itShouldSetTheAcceptHeaderAppropriatelyForTheFormat {
|
||||
@@ -85,30 +82,33 @@
|
||||
// that we just need a way to save the context before we begin mapping or something
|
||||
// on success. Always saving means that we can abandon objects on failure...
|
||||
[_objectManager.objectStore save];
|
||||
[_objectManager postObject:temporaryHuman delegate:_responseLoader];
|
||||
[_responseLoader waitForResponse];
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
[_objectManager postObject:temporaryHuman delegate:loader];
|
||||
[loader waitForResponse];
|
||||
|
||||
RKHuman* human = (RKHuman*)[_responseLoader.objects objectAtIndex:0];
|
||||
RKHuman* human = (RKHuman*)[loader.objects objectAtIndex:0];
|
||||
assertThat(human, is(equalTo(temporaryHuman)));
|
||||
assertThat(human.railsID, is(equalToInt(1)));
|
||||
}
|
||||
|
||||
// TODO: Move to Core Data specific spec file...
|
||||
- (void)itShouldLoadAHuman {
|
||||
[_objectManager loadObjectsAtResourcePath:@"/JSON/humans/1.json" delegate:_responseLoader];
|
||||
_responseLoader.timeout = 200;
|
||||
[_responseLoader waitForResponse];
|
||||
[expectThat(_responseLoader.failureError) should:be(nil)];
|
||||
assertThat(_responseLoader.objects, isNot(empty()));
|
||||
RKHuman* blake = (RKHuman*)[_responseLoader.objects objectAtIndex:0];
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
[_objectManager loadObjectsAtResourcePath:@"/JSON/humans/1.json" delegate:loader];
|
||||
loader.timeout = 200;
|
||||
[loader waitForResponse];
|
||||
[expectThat(loader.failureError) should:be(nil)];
|
||||
assertThat(loader.objects, isNot(empty()));
|
||||
RKHuman* blake = (RKHuman*)[loader.objects objectAtIndex:0];
|
||||
NSLog(@"Blake: %@ (name = %@)", blake, blake.name);
|
||||
[expectThat(blake.name) should:be(@"Blake Watters")];
|
||||
}
|
||||
|
||||
- (void)itShouldLoadAllHumans {
|
||||
[_objectManager loadObjectsAtResourcePath:@"/JSON/humans/all.json" delegate:_responseLoader];
|
||||
[_responseLoader waitForResponse];
|
||||
NSArray* humans = (NSArray*) _responseLoader.objects;
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
[_objectManager loadObjectsAtResourcePath:@"/JSON/humans/all.json" delegate:loader];
|
||||
[loader waitForResponse];
|
||||
NSArray* humans = (NSArray*) loader.objects;
|
||||
[expectThat([humans count]) should:be(2)];
|
||||
[expectThat([[humans objectAtIndex:0] class]) should:be([RKHuman class])];
|
||||
}
|
||||
@@ -116,9 +116,10 @@
|
||||
- (void)itShouldHandleConnectionFailures {
|
||||
NSString* localBaseURL = [NSString stringWithFormat:@"http://127.0.0.1:3001"];
|
||||
RKObjectManager* modelManager = [RKObjectManager objectManagerWithBaseURL:localBaseURL];
|
||||
[modelManager loadObjectsAtResourcePath:@"/JSON/humans/1" delegate:_responseLoader];
|
||||
[_responseLoader waitForResponse];
|
||||
[expectThat(_responseLoader.success) should:be(NO)];
|
||||
RKSpecResponseLoader* loader = [RKSpecResponseLoader responseLoader];
|
||||
[modelManager loadObjectsAtResourcePath:@"/JSON/humans/1" delegate:loader];
|
||||
[loader waitForResponse];
|
||||
[expectThat(loader.success) should:be(NO)];
|
||||
}
|
||||
|
||||
- (void)itShouldPOSTAnObject {
|
||||
|
||||
@@ -49,6 +49,9 @@ RKObjectManager* RKSpecNewObjectManager() {
|
||||
[RKClient setSharedClient:objectManager.client];
|
||||
RKSpecNewRequestQueue();
|
||||
|
||||
// This allows the manager to determine state.
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
|
||||
return objectManager;
|
||||
}
|
||||
|
||||
|
||||
22
Specs/Server/lib/restkit/network/etags.rb
Normal file
22
Specs/Server/lib/restkit/network/etags.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
module RestKit
|
||||
module Network
|
||||
class ETags < Sinatra::Base
|
||||
|
||||
get '/etags' do
|
||||
"Success!"
|
||||
end
|
||||
|
||||
get '/etags/cached' do
|
||||
tag = "686897696a7c876b7e"
|
||||
if tag == request.env["HTTP_IF_NONE_MATCH"]
|
||||
status 304
|
||||
""
|
||||
else
|
||||
headers "ETag" => tag
|
||||
"This Should Get Cached"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,10 +10,12 @@ Debugger.start
|
||||
# Import the RestKit Spec server
|
||||
$: << File.join(File.expand_path(File.dirname(__FILE__)), 'lib')
|
||||
require 'restkit/network/authentication'
|
||||
require 'restkit/network/etags'
|
||||
|
||||
class RestKit::SpecServer < Sinatra::Base
|
||||
self.app_file = __FILE__
|
||||
use RestKit::Network::Authentication
|
||||
use RestKit::Network::ETags
|
||||
|
||||
configure do
|
||||
set :logging, true
|
||||
|
||||
82
Vendor/JSONKit/JSONKit.h
vendored
82
Vendor/JSONKit/JSONKit.h
vendored
@@ -1,7 +1,8 @@
|
||||
//
|
||||
// JSONKit.h
|
||||
// http://github.com/johnezang/JSONKit
|
||||
// Licensed under the terms of the BSD License, as specified below.
|
||||
// Dual licensed under either the terms of the BSD License, or alternatively
|
||||
// under the terms of the Apache License, Version 2.0, as specified below.
|
||||
//
|
||||
|
||||
/*
|
||||
@@ -36,6 +37,22 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2011 John Engelhart
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
@@ -109,10 +126,11 @@ enum {
|
||||
typedef JKFlags JKParseOptionFlags;
|
||||
|
||||
enum {
|
||||
JKSerializeOptionNone = 0,
|
||||
JKSerializeOptionPretty = (1 << 0), // Not implemented yet...
|
||||
JKSerializeOptionEscapeUnicode = (1 << 1),
|
||||
JKSerializeOptionValidFlags = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode),
|
||||
JKSerializeOptionNone = 0,
|
||||
JKSerializeOptionPretty = (1 << 0),
|
||||
JKSerializeOptionEscapeUnicode = (1 << 1),
|
||||
JKSerializeOptionEscapeForwardSlashes = (1 << 4),
|
||||
JKSerializeOptionValidFlags = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode | JKSerializeOptionEscapeForwardSlashes),
|
||||
};
|
||||
typedef JKFlags JKSerializeOptionFlags;
|
||||
|
||||
@@ -153,7 +171,11 @@ typedef struct JKParseState JKParseState; // Opaque internal, private type.
|
||||
|
||||
@end
|
||||
|
||||
@interface NSString (JSONKit)
|
||||
////////////
|
||||
#pragma mark Deserializing methods
|
||||
////////////
|
||||
|
||||
@interface NSString (JSONKitDeserializing)
|
||||
- (id)objectFromJSONString;
|
||||
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
|
||||
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
|
||||
@@ -162,7 +184,7 @@ typedef struct JKParseState JKParseState; // Opaque internal, private type.
|
||||
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@interface NSData (JSONKit)
|
||||
@interface NSData (JSONKitDeserializing)
|
||||
// The NSData MUST be UTF8 encoded JSON.
|
||||
- (id)objectFromJSONData;
|
||||
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
|
||||
@@ -172,20 +194,54 @@ typedef struct JKParseState JKParseState; // Opaque internal, private type.
|
||||
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@interface NSArray (JSONKit)
|
||||
- (NSData *)JSONData;
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
- (NSString *)JSONString;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
////////////
|
||||
#pragma mark Serializing methods
|
||||
////////////
|
||||
|
||||
@interface NSString (JSONKitSerializing)
|
||||
// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString).
|
||||
// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes:
|
||||
// includeQuotes:YES `a "test"...` -> `"a \"test\"..."`
|
||||
// includeQuotes:NO `a "test"...` -> `a \"test\"...`
|
||||
- (NSData *)JSONData; // Invokes JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
|
||||
- (NSString *)JSONString; // Invokes JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@interface NSDictionary (JSONKit)
|
||||
@interface NSArray (JSONKitSerializing)
|
||||
- (NSData *)JSONData;
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
|
||||
- (NSString *)JSONString;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@interface NSDictionary (JSONKitSerializing)
|
||||
- (NSData *)JSONData;
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
|
||||
- (NSString *)JSONString;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
|
||||
@end
|
||||
|
||||
#ifdef __BLOCKS__
|
||||
|
||||
@interface NSArray (JSONKitSerializingBlockAdditions)
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
|
||||
@end
|
||||
|
||||
@interface NSDictionary (JSONKitSerializingBlockAdditions)
|
||||
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
|
||||
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif // __OBJC__
|
||||
|
||||
#endif // _JSONKIT_H_
|
||||
|
||||
3
Vendor/JSONKit/JSONKit.m
vendored
3
Vendor/JSONKit/JSONKit.m
vendored
@@ -1005,7 +1005,8 @@ static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash,
|
||||
|
||||
static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey) {
|
||||
NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity));
|
||||
if(aKey == NULL) { return(NULL); }
|
||||
// Guard against a divide by zero here.
|
||||
if(aKey == NULL || 0 == dictionary->capacity) { return(NULL); }
|
||||
NSUInteger keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL;
|
||||
JKHashTableEntry *atEntry = NULL;
|
||||
for(idx = 0UL; idx < dictionary->capacity; idx++) {
|
||||
|
||||
Reference in New Issue
Block a user