Merge branch 'reachability-queue-three20'

Conflicts:
	Code/Network/RKResponse.m
This commit is contained in:
Blake Watters
2010-12-23 14:10:17 -05:00
29 changed files with 1177 additions and 830 deletions

View File

@@ -88,7 +88,7 @@
+ (id)object;
/**
* The name of an object mapped property existing on this classs representing the unique primary key.
* The name of an object mapped property existing on this class representing the unique primary key.
* Must be implemented by the subclass for the mapper to be able to uniquely identify objects.
*/
+ (NSString*)primaryKeyProperty;

View File

@@ -10,3 +10,5 @@
#import "RKRequest.h"
#import "RKResponse.h"
#import "RKRequestSerializable.h"
#import "RKReachabilityObserver.h"
#import "RKRequestQueue.h"

View File

@@ -10,6 +10,7 @@
#import "RKParams.h"
#import "RKResponse.h"
#import "NSDictionary+RKRequestSerialization.h"
#import "RKReachabilityObserver.h"
/**
* RKClient exposes the low level client interface for working
@@ -21,6 +22,7 @@
NSString* _username;
NSString* _password;
NSMutableDictionary* _HTTPHeaders;
RKReachabilityObserver* _baseURLReachabilityObserver;
}
/**
@@ -43,6 +45,12 @@
*/
@property(nonatomic, readonly) NSDictionary* HTTPHeaders;
/**
* The RKReachabilityObserver used to monitor whether or not the client has a connection
* path to the baseURL
*/
@property(nonatomic, readonly) RKReachabilityObserver* baseURLReachabilityObserver;
/**
* Return the configured singleton instance of the Rest client
*/
@@ -95,11 +103,13 @@
*/
- (NSURL*)URLForResourcePath:(NSString *)resourcePath queryParams:(NSDictionary*)queryParams;
- (void)setupRequest:(RKRequest*)request;
/**
* Return a request object targetted at a resource path relative to the base URL. By default the method is set to GET
* All headers set on the client will automatically be applied to the request as well.
*/
- (RKRequest*)requestWithResourcePath:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)requestWithResourcePath:(NSString*)resourcePath delegate:(id)delegate;
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Asynchronous Helper Methods
@@ -112,31 +122,31 @@
*/
/**
* Fetch a resource via an HTTP GET and invoke a callback with the result
* Fetch a resource via an HTTP GET
*/
- (RKRequest*)get:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)get:(NSString*)resourcePath delegate:(id)delegate;
/**
* Fetch a resource via an HTTP GET with a dictionary of params and invoke a callback with the resulting payload
* Fetch a resource via an HTTP GET with a dictionary of params
*
* Note that this request _only_ allows NSDictionary objects as the params. The dictionary will be coerced into a URL encoded
* string and then appended to the resourcePath as the query string of the request.
*/
- (RKRequest*)get:(NSString*)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)get:(NSString*)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id)delegate;
/**
* Create a resource via an HTTP POST with a set of form parameters and invoke a callback with the resulting payload
* Create a resource via an HTTP POST with a set of form parameters
*/
- (RKRequest*)post:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)post:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate;
/**
* Update a resource via an HTTP PUT and invoke a callback with the resulting payload
* Update a resource via an HTTP PUT
*/
- (RKRequest*)put:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)put:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate;
/**
* Destroy a resource via an HTTP DELETE and invoke a callback with the resulting payload
* Destroy a resource via an HTTP DELETE
*/
- (RKRequest*)delete:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback;
- (RKRequest*)delete:(NSString*)resourcePath delegate:(id)delegate;
@end

View File

@@ -24,6 +24,7 @@ static RKClient* sharedClient = nil;
@synthesize username = _username;
@synthesize password = _password;
@synthesize HTTPHeaders = _HTTPHeaders;
@synthesize baseURLReachabilityObserver = _baseURLReachabilityObserver;
+ (RKClient*)sharedClient {
return sharedClient;
@@ -80,20 +81,13 @@ static RKClient* sharedClient = nil;
}
- (BOOL)isNetworkAvailable {
Boolean success;
const char *host_name = "google.com"; // your data source host name
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, host_name);
#ifdef TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
SCNetworkReachabilityFlags flags;
#else
SCNetworkConnectionFlags flags;
#endif
success = SCNetworkReachabilityGetFlags(reachability, &flags);
BOOL isNetworkAvailable = success && (flags & kSCNetworkFlagsReachable) && !(flags & kSCNetworkFlagsConnectionRequired);
CFRelease(reachability);
BOOL isNetworkAvailable = NO;
if (self.baseURLReachabilityObserver) {
isNetworkAvailable = [self.baseURLReachabilityObserver isNetworkReachable];
} else {
RKReachabilityObserver* googleObserver = [RKReachabilityObserver reachabilityObserverWithHostName:@"google.com"];
isNetworkAvailable = [googleObserver isNetworkReachable];
}
return isNetworkAvailable;
}
@@ -119,8 +113,18 @@ static RKClient* sharedClient = nil;
[_HTTPHeaders setValue:value forKey:header];
}
- (RKRequest*)requestWithResourcePath:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback {
RKRequest* request = [[RKRequest alloc] initWithURL:[self URLForResourcePath:resourcePath] delegate:delegate callback:callback];
- (void)setBaseURL:(NSString*)baseURL {
[_baseURL release];
_baseURL = nil;
_baseURL = [baseURL retain];
[_baseURLReachabilityObserver release];
_baseURLReachabilityObserver = nil;
_baseURLReachabilityObserver = [[RKReachabilityObserver reachabilityObserverWithHostName:baseURL] retain];
}
- (RKRequest*)requestWithResourcePath:(NSString*)resourcePath delegate:(id)delegate {
RKRequest* request = [[RKRequest alloc] initWithURL:[self URLForResourcePath:resourcePath] delegate:delegate];
[self setupRequest:request];
[request autorelease];
@@ -131,8 +135,8 @@ static RKClient* sharedClient = nil;
// Asynchronous Requests
///////////////////////////////////////////////////////////////////////////////////////////////////////////
- (RKRequest*)load:(NSString*)resourcePath method:(RKRequestMethod)method params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate callback:(SEL)callback {
RKRequest* request = [[RKRequest alloc] initWithURL:[self URLForResourcePath:resourcePath] delegate:delegate callback:callback];
- (RKRequest*)load:(NSString*)resourcePath method:(RKRequestMethod)method params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate {
RKRequest* request = [[RKRequest alloc] initWithURL:[self URLForResourcePath:resourcePath] delegate:delegate];
[self setupRequest:request];
[request autorelease];
request.params = params;
@@ -142,25 +146,25 @@ static RKClient* sharedClient = nil;
return request;
}
- (RKRequest*)get:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback {
return [self load:resourcePath method:RKRequestMethodGET params:nil delegate:delegate callback:callback];
- (RKRequest*)get:(NSString*)resourcePath delegate:(id)delegate {
return [self load:resourcePath method:RKRequestMethodGET params:nil delegate:delegate];
}
- (RKRequest*)get:(NSString*)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id)delegate callback:(SEL)callback {
- (RKRequest*)get:(NSString*)resourcePath queryParams:(NSDictionary*)queryParams delegate:(id)delegate {
NSString* resourcePathWithQueryString = [NSString stringWithFormat:@"%@?%@", resourcePath, [queryParams URLEncodedString]];
return [self load:resourcePathWithQueryString method:RKRequestMethodGET params:nil delegate:delegate callback:callback];
return [self load:resourcePathWithQueryString method:RKRequestMethodGET params:nil delegate:delegate];
}
- (RKRequest*)post:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate callback:(SEL)callback {
return [self load:resourcePath method:RKRequestMethodPOST params:params delegate:delegate callback:callback];
- (RKRequest*)post:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate {
return [self load:resourcePath method:RKRequestMethodPOST params:params delegate:delegate];
}
- (RKRequest*)put:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate callback:(SEL)callback {
return [self load:resourcePath method:RKRequestMethodPUT params:params delegate:delegate callback:callback];
- (RKRequest*)put:(NSString*)resourcePath params:(NSObject<RKRequestSerializable>*)params delegate:(id)delegate {
return [self load:resourcePath method:RKRequestMethodPUT params:params delegate:delegate];
}
- (RKRequest*)delete:(NSString*)resourcePath delegate:(id)delegate callback:(SEL)callback {
return [self load:resourcePath method:RKRequestMethodDELETE params:nil delegate:delegate callback:callback];
- (RKRequest*)delete:(NSString*)resourcePath delegate:(id)delegate {
return [self load:resourcePath method:RKRequestMethodDELETE params:nil delegate:delegate];
}
@end

View File

@@ -18,3 +18,4 @@
*/
extern NSString* const kRKRequestSentNotification;
extern NSString* const kRKResponseReceivedNotification;
extern NSString* const kRKRequestFailedWithErrorNotification;

View File

@@ -10,3 +10,4 @@
NSString* const kRKRequestSentNotification = @"kRKRequestSentNotification";
NSString* const kRKResponseReceivedNotification = @"kRKRespongReceivedNotification";
NSString* const kRKRequestFailedWithErrorNotification = @"kRKRequestFailedWithErrorNotification";

View File

@@ -0,0 +1,55 @@
//
// RKReachabilityObserver.h
// RestKit
//
// Created by Blake Watters on 9/14/10.
// Copyright 2010 RestKit. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/**
* Posted when the network state has changed
*/
extern NSString* const RKReachabilityStateChangedNotification;
typedef enum {
RKReachabilityNotReachable,
RKReachabilityReachableViaWiFi,
RKReachabilityReachableViaWWAN
} RKReachabilityNetworkStatus;
/**
* Provides a notification based interface for monitoring changes
* to network status
*/
@interface RKReachabilityObserver : NSObject {
SCNetworkReachabilityRef _reachabilityRef;
}
/**
* Create a new reachability observer against a given hostname. The observer
* will monitor the ability to reach the specified hostname and emit notifications
* when its reachability status changes.
*
* Note that the observer will be scheduled in the current run loop.
*/
+ (RKReachabilityObserver*)reachabilityObserverWithHostName:(NSString*)hostName;
/**
* Returns the current network status
*/
- (RKReachabilityNetworkStatus)networkStatus;
/**
* Returns YES when the Internet is reachable (via WiFi or WWAN)
*/
- (BOOL)isNetworkReachable;
/**
* Returns YES when WWAN may be available, but not active until a connection has been established.
*/
- (BOOL)isConnectionRequired;
@end

View File

@@ -0,0 +1,145 @@
//
// RKReachabilityObserver.m
// RestKit
//
// Created by Blake Watters on 9/14/10.
// Copyright 2010 RestKit. All rights reserved.
//
#import "RKReachabilityObserver.h"
#import <UIKit/UIKit.h>
// Constants
NSString* const RKReachabilityStateChangedNotification = @"RKReachabilityStateChangedNotification";
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) {
#pragma unused (target, flags)
// We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively
// in case someone uses the Reachablity object in a different thread.
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
RKReachabilityObserver* observer = (RKReachabilityObserver*) info;
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName:RKReachabilityStateChangedNotification object:observer];
[pool release];
}
#pragma mark -
@interface RKReachabilityObserver (Private)
// Internal initializer
- (id)initWithReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef;
- (void)scheduleObserver;
- (void)unscheduleObserver;
@end
@implementation RKReachabilityObserver
+ (RKReachabilityObserver*)reachabilityObserverWithHostName:(NSString*)hostName {
RKReachabilityObserver* observer = nil;
SCNetworkReachabilityRef reachabilityRef = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
if (nil != reachabilityRef) {
observer = [[[self alloc] initWithReachabilityRef:reachabilityRef] autorelease];
}
return observer;
}
- (id)initWithReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef {
if (self = [self init]) {
_reachabilityRef = reachabilityRef;
[self scheduleObserver];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(scheduleObserver)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(unscheduleObserver)
name:UIApplicationWillResignActiveNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self unscheduleObserver];
if (_reachabilityRef) {
CFRelease(_reachabilityRef);
}
[super dealloc];
}
- (RKReachabilityNetworkStatus)networkStatus {
NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef");
RKReachabilityNetworkStatus status = RKReachabilityNotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) {
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) {
// if target host is not reachable
return RKReachabilityNotReachable;
}
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) {
// if target host is reachable and no connection is required
// then we'll assume (for now) that your on Wi-Fi
status = RKReachabilityReachableViaWiFi;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) {
// ... and the connection is on-demand (or on-traffic) if the
// calling application is using the CFSocketStream or higher APIs
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) {
// ... and no [user] intervention is needed
status = RKReachabilityReachableViaWiFi;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) {
// ... but WWAN connections are OK if the calling application
// is using the CFNetwork (CFSocketStream?) APIs.
status = RKReachabilityReachableViaWWAN;
}
}
return status;
}
- (BOOL)isNetworkReachable {
return (RKReachabilityNotReachable != [self networkStatus]);
}
- (BOOL)isConnectionRequired {
NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) {
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
#pragma mark Observer scheduling
- (void)scheduleObserver {
SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) {
if (NO == SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) {
NSLog(@"Warning -- Unable to schedule reachability observer in current run loop.");
}
}
}
- (void)unscheduleObserver {
if (nil != _reachabilityRef) {
SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
}
@end

View File

@@ -22,6 +22,7 @@ typedef enum RKRequestMethod {
} RKRequestMethod;
@class RKResponse;
@protocol RKRequestDelegate;
@interface RKRequest : NSObject {
NSURL* _URL;
@@ -29,12 +30,13 @@ typedef enum RKRequestMethod {
NSURLConnection* _connection;
NSDictionary* _additionalHTTPHeaders;
NSObject<RKRequestSerializable>* _params;
id _delegate;
SEL _callback;
NSObject<RKRequestDelegate>* _delegate;
id _userData;
NSString* _username;
NSString* _password;
RKRequestMethod _method;
BOOL _isLoading;
BOOL _isLoaded;
}
/**
@@ -42,6 +44,11 @@ typedef enum RKRequestMethod {
*/
@property(nonatomic, readonly) NSURL* URL;
/**
* The resourcePath portion of this loader's URL
*/
@property (nonatomic, readonly) NSString* resourcePath;
/**
* The HTTP verb the request is sent via
*
@@ -61,12 +68,7 @@ typedef enum RKRequestMethod {
* If the object implements the RKRequestDelegate protocol,
* it will receive request lifecycle event messages.
*/
@property(nonatomic, assign) id delegate;
/**
* The selector to invoke when the request is completed
*/
@property(nonatomic, assign) SEL callback;
@property(nonatomic, assign) NSObject<RKRequestDelegate>* delegate;
/**
* A Dictionary of additional HTTP Headers to send with the request
@@ -100,7 +102,7 @@ typedef enum RKRequestMethod {
/**
* Return a REST request that is ready for dispatching
*/
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate callback:(SEL)callback;
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate;
/**
* Initialize a synchronous request
@@ -110,10 +112,11 @@ typedef enum RKRequestMethod {
/**
* Initialize a REST request and prepare it for dispatching
*/
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate callback:(SEL)callback;
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate;
/**
* Send the request asynchronously
* Send the request asynchronously. It will be added to the queue and
* dispatched as soon as possible.
*/
- (void)send;
@@ -122,6 +125,18 @@ typedef enum RKRequestMethod {
*/
- (RKResponse*)sendSynchronously;
/**
* Callback performed to notify the request that the underlying NSURLConnection
* has failed with an error.
*/
- (void)didFailLoadWithError:(NSError*)error;
/**
* Callback performed to notify the request that the underlying NSURLConnection
* has completed with a response.
*/
- (void)didFinishLoad:(RKResponse*)response;
/**
* Cancels the underlying URL connection
*/
@@ -147,6 +162,16 @@ typedef enum RKRequestMethod {
*/
- (BOOL)isDELETE;
/**
* Returns YES when this request is in-progress
*/
- (BOOL)isLoading;
/**
* Returns YES when this request has been completed
*/
- (BOOL)isLoaded;
@end
/**
@@ -154,18 +179,13 @@ typedef enum RKRequestMethod {
*
* Modeled off of TTURLRequest
*/
@protocol RKRequestDelegate
@protocol RKRequestDelegate
@optional
/**
* Sent when a request has started loading
*/
- (void)requestDidStartLoad:(RKRequest*)request;
/**
* Sent when a request has finished loading
*/
- (void)requestDidFinishLoad:(RKRequest*)request;
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response;
/**
* Sent when a request has failed due to an error
@@ -173,9 +193,9 @@ typedef enum RKRequestMethod {
- (void)request:(RKRequest*)request didFailLoadWithError:(NSError*)error;
/**
* Sent when a request has been canceled
* Sent when a request has started loading
*/
- (void)requestDidCancelLoad:(RKRequest*)request;
- (void)requestDidStartLoad:(RKRequest*)request;
/**
* Sent when a request has uploaded data to the remote site

View File

@@ -7,20 +7,21 @@
//
#import "RKRequest.h"
#import "RKRequestQueue.h"
#import "RKResponse.h"
#import "NSDictionary+RKRequestSerialization.h"
#import "RKNotifications.h"
#import "RKClient.h"
#import "../Support/Support.h"
#import "RKURL.h"
@implementation RKRequest
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, callback = _callback, additionalHTTPHeaders = _additionalHTTPHeaders,
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders,
params = _params, userData = _userData, username = _username, password = _password, method = _method;
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate callback:(SEL)callback {
RKRequest* request = [[RKRequest alloc] initWithURL:URL delegate:delegate callback:callback];
[request autorelease];
return request;
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate {
return [[[RKRequest alloc] initWithURL:URL delegate:delegate] autorelease];
}
- (id)initWithURL:(NSURL*)URL {
@@ -28,30 +29,38 @@
_URL = [URL retain];
_URLRequest = [[NSMutableURLRequest alloc] initWithURL:_URL];
_connection = nil;
_isLoading = NO;
_isLoaded = NO;
}
return self;
}
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate callback:(SEL)callback {
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate {
if (self = [self initWithURL:URL]) {
_delegate = delegate;
_callback = callback;
}
return self;
}
- (void)dealloc {
self.delegate = nil;
[_connection cancel];
[_connection release];
_connection = nil;
[_userData release];
_userData = nil;
[_URL release];
_URL = nil;
[_URLRequest release];
_URLRequest = nil;
[_params release];
_params = nil;
[_additionalHTTPHeaders release];
_additionalHTTPHeaders = nil;
[_username release];
_username = nil;
[_password release];
_password = nil;
[super dealloc];
}
@@ -126,38 +135,96 @@
}
- (void)send {
[self addHeadersToRequest];
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
NSLog(@"Sending %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
[body release];
NSDate* sentAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod", [self URL], @"URL", sentAt, @"sentAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestSentNotification object:self userInfo:userInfo];
RKResponse* response = [[[RKResponse alloc] initWithRequest:self] autorelease];
_connection = [[NSURLConnection connectionWithRequest:_URLRequest delegate:response] retain];
[[RKRequestQueue sharedQueue] sendRequest:self];
}
- (void)fireAsynchronousRequest {
if ([[RKClient sharedClient] isNetworkAvailable]) {
[self addHeadersToRequest];
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
NSLog(@"Sending %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
[body release];
NSDate* sentAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod", [self URL], @"URL", sentAt, @"sentAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestSentNotification object:self userInfo:userInfo];
_isLoading = YES;
RKResponse* response = [[[RKResponse alloc] initWithRequest:self] autorelease];
_connection = [[NSURLConnection connectionWithRequest:_URLRequest delegate:response] retain];
} 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];
}
}
- (RKResponse*)sendSynchronously {
[self addHeadersToRequest];
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
NSLog(@"Sending synchronous %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
[body release];
NSDate* sentAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod", [self URL], @"URL", sentAt, @"sentAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestSentNotification object:self userInfo:userInfo];
NSURLResponse* URLResponse = nil;
NSError* error = nil;
NSData* payload = [NSURLConnection sendSynchronousRequest:_URLRequest returningResponse:&URLResponse error:&error];
return [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
NSData* payload = nil;
RKResponse* response = nil;
if ([[RKClient sharedClient] isNetworkAvailable]) {
[self addHeadersToRequest];
NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding];
NSLog(@"Sending synchronous %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body);
[body release];
NSDate* sentAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod", [self URL], @"URL", sentAt, @"sentAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestSentNotification object:self userInfo:userInfo];
_isLoading = YES;
payload = [NSURLConnection sendSynchronousRequest:_URLRequest returningResponse:&URLResponse error:&error];
response = [[[RKResponse alloc] initWithSynchronousRequest:self URLResponse:URLResponse body:payload error:error] autorelease];
} 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;
}
- (void)cancel {
[_connection cancel];
[_connection release];
_connection = nil;
if ([_delegate respondsToSelector:@selector(requestDidCancelLoad:)]) {
[_delegate requestDidCancelLoad:self];
_isLoading = NO;
}
- (void)didFailLoadWithError:(NSError*)error {
_isLoading = NO;
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
[_delegate request:self didFailLoadWithError:error];
}
NSDate* receivedAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL", receivedAt, @"receivedAt", error, @"error", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestFailedWithErrorNotification object:self userInfo:userInfo];
}
- (void)didFinishLoad:(RKResponse*)response {
_isLoading = NO;
_isLoaded = YES;
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
[_delegate request:self didLoadResponse:response];
}
NSDate* receivedAt = [NSDate date];
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod", [self URL], @"URL", receivedAt, @"receivedAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:response userInfo:userInfo];
}
- (BOOL)isGET {
@@ -176,4 +243,21 @@
return _method == RKRequestMethodDELETE;
}
- (BOOL)isLoading {
return _isLoading;
}
- (BOOL)isLoaded {
return _isLoaded;
}
- (NSString*)resourcePath {
NSString* resourcePath = nil;
if ([self.URL isKindOfClass:[RKURL class]]) {
RKURL* url = (RKURL*)self.URL;
resourcePath = url.resourcePath;
}
return resourcePath;
}
@end

View File

@@ -0,0 +1,63 @@
//
// RKRequestQueue.h
// RestKit
//
// Created by Blake Watters on 12/1/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RKRequest.h"
/**
* A lightweight queue implementation responsible
* for dispatching and managing RKRequest objects
*/
@interface RKRequestQueue : NSObject {
NSMutableArray* _requests;
NSInteger _totalLoading;
NSTimer* _queueTimer;
BOOL _suspended;
}
/**
* Gets the flag that determines if new load requests are allowed to reach the network.
*
* Because network requests tend to slow down performance, this property can be used to
* temporarily delay them. All requests made while suspended are queued, and when
* suspended becomes false again they are executed.
*/
@property (nonatomic) BOOL suspended;
/**
* Return the global queue
*/
+ (RKRequestQueue*)sharedQueue;
/**
* Set the global queue
*/
+ (void)setSharedQueue:(RKRequestQueue*)requestQueue;
/**
* Add an asynchronous request to the queue and send it as
* as soon as possible
*/
- (void)sendRequest:(RKRequest*)request;
/**
* Cancel a request that is in progress
*/
- (void)cancelRequest:(RKRequest*)request;
/**
* Cancel all requests with a given delegate
*/
- (void)cancelRequestsWithDelegate:(NSObject<RKRequestDelegate>*)delegate;
/**
* Cancel all active or pending requests.
*/
- (void)cancelAllRequests;
@end

View File

@@ -0,0 +1,163 @@
//
// RKRequestQueue.m
// RestKit
//
// Created by Blake Watters on 12/1/10.
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "RKRequestQueue.h"
#import "RKResponse.h"
#import "RKNotifications.h"
static RKRequestQueue* gSharedQueue = nil;
static const NSTimeInterval kFlushDelay = 0.3;
static const NSTimeInterval kTimeout = 300.0;
static const NSInteger kMaxConcurrentLoads = 5;
@implementation RKRequestQueue
@synthesize suspended = _suspended;
+ (RKRequestQueue*)sharedQueue {
if (!gSharedQueue) {
gSharedQueue = [[RKRequestQueue alloc] init];
}
return gSharedQueue;
}
+ (void)setSharedQueue:(RKRequestQueue*)requestQueue {
if (gSharedQueue != requestQueue) {
[gSharedQueue release];
gSharedQueue = [requestQueue retain];
}
}
- (id)init {
if (self = [super init]) {
_requests = [[NSMutableArray alloc] init];
_suspended = NO;
_totalLoading = 0;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKResponseReceivedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKRequestFailedWithErrorNotification
object:nil];
}
return self;
}
- (void)dealloc {
[_queueTimer invalidate];
[_requests release];
_requests = nil;
[super dealloc];
}
- (void)loadNextInQueueDelayed {
if (!_queueTimer) {
_queueTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushDelay
target:self
selector:@selector(loadNextInQueue)
userInfo:nil
repeats:NO];
}
}
- (void)dispatchRequest:(RKRequest*)request {
[request performSelector:@selector(fireAsynchronousRequest)];
}
- (void)loadNextInQueue {
_queueTimer = nil;
for (RKRequest* request in _requests) {
if (![request isLoading] && ![request isLoaded] && _totalLoading < kMaxConcurrentLoads) {
++_totalLoading;
[self dispatchRequest:request];
}
}
if (_requests.count && !_suspended) {
[self loadNextInQueueDelayed];
}
}
- (void)setSuspended:(BOOL)isSuspended {
_suspended = isSuspended;
if (!_suspended) {
[self loadNextInQueue];
} else if (_queueTimer) {
[_queueTimer invalidate];
_queueTimer = nil;
}
}
- (void)sendRequest:(RKRequest*)request {
[_requests addObject:request];
[self loadNextInQueue];
}
- (void)cancelRequest:(RKRequest*)request loadNext:(BOOL)loadNext {
if ([_requests containsObject:request] && ![request isLoaded]) {
[request cancel];
request.delegate = nil;
[_requests removeObject:request];
_totalLoading--;
if (loadNext) {
[self loadNextInQueue];
}
}
}
- (void)cancelRequest:(RKRequest*)request {
[self cancelRequest:request loadNext:YES];
}
- (void)cancelRequestsWithDelegate:(NSObject<RKRequestDelegate>*)delegate {
NSArray* requestsCopy = [NSArray arrayWithArray:_requests];
for (RKRequest* request in requestsCopy) {
if (request.delegate && request.delegate == delegate) {
[self cancelRequest:request];
}
}
}
- (void)cancelAllRequests {
NSArray* requestsCopy = [NSArray arrayWithArray:_requests];
for (RKRequest* request in requestsCopy) {
[self cancelRequest:request loadNext:NO];
}
}
/**
* Invoked via observation when a request has loaded a response. Remove
* the completed request from the queue and continue processing
*/
- (void)responseDidLoad:(NSNotification*)notification {
if (notification.object) {
// Our RKRequest completed and we're notified with an RKResponse object
if ([notification.object isKindOfClass:[RKResponse class]]) {
RKRequest* request = [(RKResponse*)notification.object request];
[_requests removeObject:request];
_totalLoading--;
// Our RKRequest failed and we're notified with the original RKRequest object
} else if ([notification.object isKindOfClass:[RKRequest class]]) {
RKRequest* request = (RKRequest*)notification.object;
[_requests removeObject:request];
_totalLoading--;
}
[self loadNextInQueue];
}
}
@end

View File

@@ -26,7 +26,9 @@
- (id)initWithRequest:(RKRequest*)request {
if (self = [self init]) {
_request = [request retain];
// We don't retain here as we're letting RKRequestQueue manage
// request ownership
_request = request;
}
return self;
@@ -34,7 +36,9 @@
- (id)initWithSynchronousRequest:(RKRequest*)request URLResponse:(NSURLResponse*)URLResponse body:(NSData*)body error:(NSError*)error {
if (self = [super init]) {
_request = [request retain];
// TODO: Does the lack of retain here cause problems with synchronous requests, since they
// are not being retained by the RKRequestQueue??
_request = request;
_httpURLResponse = [URLResponse retain];
_failureError = [error retain];
_body = [body retain];
@@ -47,11 +51,24 @@
- (void)dealloc {
[_httpURLResponse release];
[_body release];
[_request release];
[_failureError release];
[super dealloc];
}
// Handle basic auth
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential=[NSURLCredential credentialWithUser:[NSString stringWithFormat:@"%@", _request.username]
password:[NSString stringWithFormat:@"%@", _request.password]
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential
forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (NO == _loading) {
_loading = YES;
@@ -67,25 +84,13 @@
_httpURLResponse = [response retain];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSDate* receivedAt = [NSDate date]; // TODO - Carry around this timestamp on the response or request?
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[_request HTTPMethod], @"HTTPMethod", [_request URL], @"URL", receivedAt, @"receivedAt", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:self userInfo:userInfo];
[[_request delegate] performSelector:[_request callback] withObject:self];
if ([[_request delegate] respondsToSelector:@selector(requestDidFinishLoad:)]) {
[[_request delegate] requestDidFinishLoad:_request];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[_request didFinishLoad:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
_failureError = [error retain];
[[_request delegate] performSelector:[_request callback] withObject:self];
if ([[_request delegate] respondsToSelector:@selector(request:didFailLoadWithError:)]) {
[[_request delegate] request:_request didFailLoadWithError:error];
}
[_request didFailLoadWithError:_failureError];
}
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
@@ -94,7 +99,6 @@
}
}
- (NSString*)localizedStatusCodeString {
return [NSHTTPURLResponse localizedStringForStatusCode:[self statusCode]];
}

View File

@@ -40,15 +40,14 @@
/**
* Wraps a request/response cycle and loads a remote object representation into local domain objects
*/
@interface RKObjectLoader : NSObject <RKRequestDelegate> {
@interface RKObjectLoader : RKRequest {
RKObjectMapper* _mapper;
NSObject<RKObjectLoaderDelegate>* _delegate;
RKRequest* _request;
RKResponse* _response;
NSObject<RKObjectMappable>* _source;
NSObject<RKObjectMappable>* _targetObject;
Class<RKObjectMappable> _objectClass;
NSString* _keyPath;
RKManagedObjectStore* _managedObjectStore;
NSManagedObjectID* _targetObjectID;
}
/**
@@ -56,19 +55,6 @@
*/
@property (nonatomic, readonly) RKObjectMapper* mapper;
/**
* The object to be invoked with the loaded models
*
* If this object implements life-cycle methods from the RKRequestDelegate protocol,
* events from the request will be forwarded back.
*/
@property (nonatomic, assign) NSObject<RKObjectLoaderDelegate>* delegate;
/**
* The underlying request object for this loader
*/
@property (nonatomic, retain) RKRequest* request;
/**
* The underlying response object for this loader
*/
@@ -82,25 +68,9 @@
/**
* The mappable object that generated this loader. This is used to map object
* updates back to the source object that sent the request
* updates back to the object that sent the request
*/
// TODO: This should have a better name... targetObject?
@property (nonatomic, retain) NSObject<RKObjectMappable>* source;
/**
* The URL this loader sent the request to
*/
@property (nonatomic, readonly) NSURL* URL;
/**
* The HTTP method used to send the request
*/
@property (nonatomic, assign) RKRequestMethod method;
/**
* Parameters sent with the request
*/
@property (nonatomic, retain) NSObject<RKRequestSerializable>* params;
@property (nonatomic, retain) NSObject<RKObjectMappable>* targetObject;
/*
* The keyPath property is an optional property to tell the mapper to map a subset of the response
@@ -118,21 +88,11 @@
/**
* Return an auto-released loader with with an object mapper, a request, and a delegate
*/
+ (id)loaderWithMapper:(RKObjectMapper*)mapper request:(RKRequest*)request delegate:(NSObject<RKObjectLoaderDelegate>*)delegate;
+ (id)loaderWithResourcePath:(NSString*)resourcePath mapper:(RKObjectMapper*)mapper delegate:(NSObject<RKObjectLoaderDelegate>*)delegate;
/**
* Initialize a new object loader with an object mapper, a request, and a delegate
*/
- (id)initWithMapper:(RKObjectMapper*)mapper request:(RKRequest*)request delegate:(NSObject<RKObjectLoaderDelegate>*)delegate;
/**
* Asynchronously send the object loader request
*/
- (void)send;
/**
* Synchronously send the object loader request and process the response
*/
- (void)sendSynchronously;
- (id)initWithResourcePath:(NSString*)resourcePath mapper:(RKObjectMapper*)mapper delegate:(NSObject<RKObjectLoaderDelegate>*)delegate;
@end

View File

@@ -13,102 +13,109 @@
#import "Errors.h"
#import "RKManagedObject.h"
#import "RKURL.h"
@interface RKObjectLoader (Private)
- (void)loadObjectsFromResponse:(RKResponse*)response;
@end
#import "RKNotifications.h"
@implementation RKObjectLoader
@synthesize mapper = _mapper, delegate = _delegate, request = _request, response = _response,
objectClass = _objectClass, source = _source, keyPath = _keyPath, managedObjectStore = _managedObjectStore;
@synthesize mapper = _mapper, response = _response, objectClass = _objectClass, targetObject = _targetObject,
keyPath = _keyPath, managedObjectStore = _managedObjectStore;
+ (id)loaderWithMapper:(RKObjectMapper*)mapper request:(RKRequest*)request delegate:(NSObject<RKObjectLoaderDelegate>*)delegate {
return [[[self alloc] initWithMapper:mapper request:request delegate:delegate] autorelease];
+ (id)loaderWithResourcePath:(NSString*)resourcePath mapper:(RKObjectMapper*)mapper delegate:(NSObject<RKObjectLoaderDelegate>*)delegate {
return [[[self alloc] initWithResourcePath:resourcePath mapper:mapper delegate:delegate] autorelease];
}
- (id)initWithMapper:(RKObjectMapper*)mapper request:(RKRequest*)request delegate:(NSObject<RKObjectLoaderDelegate>*)delegate {
if (self = [self init]) {
- (id)initWithResourcePath:(NSString*)resourcePath mapper:(RKObjectMapper*)mapper delegate:(NSObject<RKObjectLoaderDelegate>*)delegate {
if (self = [self initWithURL:[[RKClient sharedClient] URLForResourcePath:resourcePath] delegate:delegate]) {
_mapper = [mapper retain];
self.request = request;
self.delegate = delegate;
self.managedObjectStore = nil;
_targetObjectID = nil;
[[RKClient sharedClient] setupRequest:self];
}
return self;
}
- (void)dealloc {
_request.delegate = nil;
[_mapper release];
[_request release];
_mapper = nil;
[_response release];
_response = nil;
[_keyPath release];
_keyPath = nil;
[_targetObject release];
_targetObject = nil;
[_targetObjectID release];
_targetObjectID = nil;
self.managedObjectStore = nil;
[super dealloc];
}
- (void)setRequest:(RKRequest *)request {
[request retain];
[_request release];
_request = request;
- (void)setTargetObject:(NSObject<RKObjectMappable>*)targetObject {
[_targetObject release];
_targetObject = nil;
_targetObject = [targetObject retain];
_request.delegate = self;
_request.callback = @selector(loadObjectsFromResponse:);
[_targetObjectID release];
_targetObjectID = nil;
if ([targetObject isKindOfClass:[NSManagedObject class]]) {
_targetObjectID = [[(NSManagedObject*)targetObject objectID] retain];
}
}
#pragma mark RKRequest Proxy Methods
- (NSURL*)URL {
return self.request.URL;
}
- (RKRequestMethod)method {
return self.request.method;
}
- (void)setMethod:(RKRequestMethod)method {
self.request.method = method;
}
- (NSObject<RKRequestSerializable>*)params {
return self.request.params;
}
- (void)setParams:(NSObject<RKRequestSerializable>*)params {
self.request.params = params;
}
- (void)send {
[self retain];
[self.request send];
}
- (void)sendSynchronously {
[self retain];
RKResponse* response = [self.request sendSynchronously];
[self loadObjectsFromResponse:response];
}
#pragma mark Response Processing
- (void)responseProcessingSuccessful:(BOOL)successful withError:(NSError*)error {
_isLoading = NO;
NSDate* receivedAt = [NSDate date];
if (successful) {
_isLoaded = YES;
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL",
receivedAt, @"receivedAt",
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification
object:_response
userInfo:userInfo];
} else {
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL",
receivedAt, @"receivedAt",
error, @"error",
nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestFailedWithErrorNotification
object:self
userInfo:userInfo];
}
}
- (BOOL)encounteredErrorWhileProcessingRequest:(RKResponse*)response {
if ([response isFailure]) {
[_delegate objectLoader:self didFailWithError:response.failureError];
[self release];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:response.failureError];
[self responseProcessingSuccessful:NO withError:response.failureError];
return YES;
} else if ([response isError]) {
NSError* error = nil;
if ([response isJSON]) {
[_delegate objectLoader:self didFailWithError:[_mapper parseErrorFromString:[response bodyAsString]]];
error = [_mapper parseErrorFromString:[response bodyAsString]];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
} else {
// TODO: We've likely run into a maintenance page here. Consider adding the ability
// to put the stack into offline mode in response...
if ([_delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) {
[_delegate objectLoaderDidLoadUnexpectedResponse:self];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoaderDidLoadUnexpectedResponse:self];
}
}
[self release];
}
[self responseProcessingSuccessful:NO withError:error];
return YES;
}
return NO;
}
@@ -128,8 +135,9 @@
}
}
[_delegate objectLoader:self didLoadObjects:[NSArray arrayWithArray:objects]];
[self release];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didLoadObjects:[NSArray arrayWithArray:objects]];
[self responseProcessingSuccessful:YES withError:nil];
}
- (void)informDelegateOfObjectLoadErrorWithInfoDictionary:(NSDictionary*)dictionary {
@@ -143,11 +151,11 @@
nil];
NSError *rkError = [NSError errorWithDomain:RKRestKitErrorDomain code:RKObjectLoaderRemoteSystemError userInfo:userInfo];
[_delegate objectLoader:self didFailWithError:rkError];
[self release];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:rkError];
[self responseProcessingSuccessful:NO withError:rkError];
}
- (void)processLoadModelsInBackground:(RKResponse *)response {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
RKManagedObjectStore* objectStore = self.managedObjectStore;
@@ -158,15 +166,14 @@
* individual object instances via getObject & friends.
*/
NSArray* results = nil;
if (self.source) {
if ([self.source isKindOfClass:[NSManagedObject class]]) {
NSManagedObjectID* modelID = [(NSManagedObject*)self.source objectID];
NSManagedObject* backgroundThreadModel = [self.managedObjectStore objectWithID:modelID];
if (self.targetObject) {
if (_targetObjectID) {
NSManagedObject* backgroundThreadModel = [self.managedObjectStore objectWithID:_targetObjectID];
[_mapper mapObject:backgroundThreadModel fromString:[response bodyAsString]];
results = [NSArray arrayWithObject:backgroundThreadModel];
} else {
[_mapper mapObject:self.source fromString:[response bodyAsString]];
results = [NSArray arrayWithObject:self.source];
[_mapper mapObject:self.targetObject fromString:[response bodyAsString]];
results = [NSArray arrayWithObject:self.targetObject];
}
} else {
id result = [_mapper mapFromString:[response bodyAsString] toClass:self.objectClass keyPath:_keyPath];
@@ -199,7 +206,7 @@
NSError* error = [objectStore save];
if (nil != error) {
NSDictionary* infoDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:response, @"response", error, @"error", nil] retain];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadErrorWithInfoDictionary:) withObject:infoDictionary waitUntilDone:NO];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadErrorWithInfoDictionary:) withObject:infoDictionary waitUntilDone:YES];
} else {
// NOTE: Passing Core Data objects across threads is not safe.
// Iterate over each model and coerce Core Data objects into ID's to pass across the threads.
@@ -214,15 +221,29 @@
}
NSDictionary* infoDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:response, @"response", models, @"models", nil] retain];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadWithInfoDictionary:) withObject:infoDictionary waitUntilDone:NO];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadWithInfoDictionary:) withObject:infoDictionary waitUntilDone:YES];
}
[pool release];
[pool drain];
}
- (void)loadObjectsFromResponse:(RKResponse*)response {
- (void)didFailLoadWithError:(NSError*)error {
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
[_delegate request:self didFailLoadWithError:error];
}
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
[self responseProcessingSuccessful:NO withError:error];
}
- (void)didFinishLoad:(RKResponse*)response {
_response = [response retain];
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
[_delegate request:self didLoadResponse:response];
}
if (NO == [self encounteredErrorWhileProcessingRequest:response]) {
// TODO: When other mapping formats are supported, unwind this assumption...
if ([response isSuccessful] && [response isJSON]) {
@@ -230,47 +251,11 @@
} else {
NSLog(@"Encountered unexpected response code: %d (MIME Type: %@)", response.statusCode, response.MIMEType);
if ([_delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) {
[_delegate objectLoaderDidLoadUnexpectedResponse:self];
[self release];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoaderDidLoadUnexpectedResponse:self];
}
[self responseProcessingSuccessful:NO withError:nil];
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RKRequestDelegate
//
// If our delegate responds to the messages, forward them back...
- (void)requestDidStartLoad:(RKRequest*)request {
if ([_delegate respondsToSelector:@selector(requestDidStartLoad:)]) {
[_delegate requestDidStartLoad:request];
}
}
- (void)requestDidFinishLoad:(RKRequest*)request {
if ([_delegate respondsToSelector:@selector(requestDidFinishLoad:)]) {
[(NSObject<RKRequestDelegate>*)_delegate requestDidFinishLoad:request];
}
}
- (void)request:(RKRequest*)request didFailLoadWithError:(NSError*)error {
if ([_delegate respondsToSelector:@selector(request:didFailLoadWithError:)]) {
[(NSObject<RKRequestDelegate>*)_delegate request:request didFailLoadWithError:error];
}
}
- (void)requestDidCancelLoad:(RKRequest*)request {
[self release];
if ([_delegate respondsToSelector:@selector(requestDidCancelLoad:)]) {
[(NSObject<RKRequestDelegate>*)_delegate requestDidCancelLoad:request];
}
}
- (void)request:(RKRequest*)request didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
if ([_delegate respondsToSelector:@selector(request:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) {
[(NSObject<RKRequestDelegate>*)_delegate request:request didSendBodyData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
}
@end

View File

@@ -15,6 +15,12 @@
extern NSString* const RKDidEnterOfflineModeNotification;
extern NSString* const RKDidEnterOnlineModeNotification;
typedef enum {
RKObjectManagerOnlineStateUndetermined,
RKObjectManagerOnlineStateDisconnected,
RKObjectManagerOnlineStateConnected
} RKObjectManagerOnlineState;
// TODO: Factor out into a protocol...
// insertObject:, deleteObject:, save, etc.
@class RKManagedObjectStore;
@@ -24,8 +30,9 @@ extern NSString* const RKDidEnterOnlineModeNotification;
RKMappingFormat _format;
RKObjectMapper* _mapper;
NSObject<RKRouter>* _router;
RKManagedObjectStore* _objectStore;
BOOL _isOnline;
RKManagedObjectStore* _objectStore;
RKObjectManagerOnlineState _onlineState;
BOOL _onlineStateForced;
}
/**

View File

@@ -32,7 +32,12 @@ static RKObjectManager* sharedManager = nil;
_router = [[RKDynamicRouter alloc] init];
_client = [[RKClient clientWithBaseURL:baseURL] retain];
self.format = RKMappingFormatJSON;
_isOnline = YES;
_onlineState = RKObjectManagerOnlineStateUndetermined;
_onlineStateForced = NO;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)
name:RKReachabilityStateChangedNotification
object:nil];
}
return self;
}
@@ -68,6 +73,7 @@ static RKObjectManager* sharedManager = nil;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_mapper release];
_mapper = nil;
[_router release];
@@ -80,23 +86,39 @@ static RKObjectManager* sharedManager = nil;
}
- (void)goOffline {
_isOnline = NO;
_onlineState = RKObjectManagerOnlineStateDisconnected;
_onlineStateForced = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RKDidEnterOfflineModeNotification object:self];
}
- (void)goOnline {
_isOnline = YES;
_onlineState = RKObjectManagerOnlineStateConnected;
_onlineStateForced = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RKDidEnterOnlineModeNotification object:self];
}
- (BOOL)isOnline {
return _isOnline;
return (_onlineState == RKObjectManagerOnlineStateConnected);
}
- (BOOL)isOffline {
return ![self isOnline];
}
- (void)reachabilityChanged:(NSNotification*)notification {
if (!_onlineStateForced) {
BOOL isHostReachable = [self.client.baseURLReachabilityObserver isNetworkReachable];
_onlineState = isHostReachable ? RKObjectManagerOnlineStateConnected : RKObjectManagerOnlineStateDisconnected;
if (isHostReachable) {
[[NSNotificationCenter defaultCenter] postNotificationName:RKDidEnterOnlineModeNotification object:self];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:RKDidEnterOfflineModeNotification object:self];
}
}
}
- (void)setFormat:(RKMappingFormat)format {
_format = format;
_mapper.format = format;
@@ -114,13 +136,7 @@ static RKObjectManager* sharedManager = nil;
}
- (RKObjectLoader*)objectLoaderWithResourcePath:(NSString*)resourcePath delegate:(NSObject<RKObjectLoaderDelegate>*)delegate {
if ([self isOffline]) {
return nil;
}
// Grab request through client to get HTTP AUTH & Headers
RKRequest* request = [self.client requestWithResourcePath:resourcePath delegate:nil callback:nil];
RKObjectLoader* loader = [RKObjectLoader loaderWithMapper:self.mapper request:request delegate:delegate];
RKObjectLoader* loader = [RKObjectLoader loaderWithResourcePath:resourcePath mapper:self.mapper delegate:delegate];
loader.managedObjectStore = self.objectStore;
return loader;
@@ -185,7 +201,7 @@ static RKObjectManager* sharedManager = nil;
loader.method = method;
loader.params = params;
loader.source = object;
loader.targetObject = object;
loader.objectClass = [object class];
loader.managedObjectStore = self.objectStore;

View File

@@ -104,7 +104,13 @@ static const NSString* kRKModelMapperMappingFormatParserKey = @"RKMappingFormatP
}
NSLog(@"Began parse...");
id result = [parser objectFromString:string];
id result = nil;
@try {
result = [parser objectFromString:string];
}
@catch (NSException* e) {
NSLog(@"[RestKit] RKObjectMapper:parseString: Exception (%@) parsing error from string: %@", [e reason], string);
}
NSLog(@"Finished parse...");
return result;
}
@@ -165,23 +171,25 @@ static const NSString* kRKModelMapperMappingFormatParserKey = @"RKMappingFormatP
// TODO: Should accept RKObjectMappable instead of id...
- (void)mapObject:(id)model fromDictionary:(NSDictionary*)dictionary {
Class class = [model class];
NSString* elementName = [_elementToClassMappings keyForObject:class];
if (elementName) {
// Extract elements nested in dictionary
if ([[dictionary allKeys] containsObject:elementName]) {
NSDictionary* elements = [dictionary objectForKey:elementName];
[self updateModel:model fromElements:elements];
} else {
// If the dictionary is not namespaced, attempt mapping its properties directly...
[self updateModel:model fromElements:dictionary];
}
} else {
NSArray* elementNames = [_elementToClassMappings allKeysForObject:class];
if ([elementNames count] == 0) {
if ([model conformsToProtocol:@protocol(RKObjectMappable)]) {
[self updateModel:model fromElements:dictionary];
} else {
[NSException raise:@"Unable to map from requested dictionary"
format:@"There was no mappable element found for objects of type %@", class];
}
}
} else {
for (NSString* elementName in elementNames) {
if ([[dictionary allKeys] containsObject:elementName]) {
NSDictionary* elements = [dictionary objectForKey:elementName];
[self updateModel:model fromElements:elements];
return;
}
}
// If the dictionary is not namespaced, attempt mapping its properties directly...
[self updateModel:model fromElements:dictionary];
}
}

View File

@@ -10,5 +10,6 @@
extern NSString* const RKRestKitErrorDomain;
typedef enum {
RKObjectLoaderRemoteSystemError = 1
RKObjectLoaderRemoteSystemError = 1,
RKRequestBaseURLOfflineError
} RKRestKitError;

View File

@@ -17,9 +17,4 @@
*/
+ (id)dictionaryWithKeysAndObjects:(id)firstKey, ... NS_REQUIRES_NIL_TERMINATION;
/**
* Returns the key for an object within the dictionary
*/
- (id)keyForObject:(id)object;
@end

View File

@@ -25,13 +25,4 @@
return [self dictionaryWithObjects:values forKeys:keys];
}
- (id)keyForObject:(id)object {
for (id key in self) {
if ([[self objectForKey:key] isEqual:object]) {
return key;
}
}
return nil;
}
@end

View File

@@ -19,6 +19,7 @@
NSArray* _sortDescriptors;
NSString* _searchText;
SEL _sortSelector;
NSArray* _filteredObjects;
}
/**
@@ -62,6 +63,6 @@
/**
* Search the model for matching text
*/
- (void)search:(NSString *)text;
- (void)search:(NSString*)text;
@end

View File

@@ -17,27 +17,29 @@
- (id)init {
if (self = [super init]) {
_predicate = nil;
_sortDescriptors = nil;
self.predicate = nil;
self.sortDescriptors = nil;
self.searchEngine = nil;
_searchText = nil;
_searchEngine = nil;
_filteredObjects = nil;
}
return self;
}
- (void)dealloc {
[_searchEngine release];_searchEngine=nil;
[_predicate release];_predicate=nil;
[_sortDescriptors release];_sortDescriptors=nil;
[_searchText release];_searchText=nil;
self.predicate = nil;
self.sortDescriptors = nil;
self.searchEngine = nil;
[_searchText release];
_searchText = nil;
[super dealloc];
}
- (void)reset {
[_predicate release];_predicate=nil;
[_sortDescriptors release];_sortDescriptors=nil;
[_searchText release];_searchText=nil;
self.predicate = nil;
self.sortDescriptors = nil;
[_searchText release];
_searchText = nil;
[self didChange];
}
@@ -53,7 +55,7 @@
return _searchEngine;
}
- (NSArray*)search:(NSString *)text inCollection:(NSArray*)collection {
- (NSArray*)search:(NSString*)text inCollection:(NSArray*)collection {
if (text.length) {
RKSearchEngine* searchEngine = [self createSearchEngine];
return [searchEngine searchFor:text inCollection:collection];
@@ -62,32 +64,85 @@
}
}
// public
- (void)search:(NSString *)text {
- (void)search:(NSString*)text {
[_searchText release];
_searchText = nil;
_searchText = [text retain];
[_filteredObjects release];
_filteredObjects = nil;
[self didFinishLoad];
}
// Overloaded to hide filtering/searching from the underlying data source
- (void)filterRawObjects {
NSArray* results = _objects;
if (results && [results count] > 0) {
if (self.predicate) {
results = [results filteredArrayUsingPredicate:self.predicate];
}
if (_searchText) {
results = [self search:_searchText inCollection:results];
}
if (self.sortSelector) {
results = [results sortedArrayUsingSelector:self.sortSelector];
} else if (self.sortDescriptors) {
results = [results sortedArrayUsingDescriptors:self.sortDescriptors];
}
_filteredObjects = [results retain];
} else {
_filteredObjects = [results copy];
}
}
- (NSArray*)objects {
NSArray* results = _model.objects;
if (self.predicate) {
results = [results filteredArrayUsingPredicate:self.predicate];
if (nil == _filteredObjects) {
[self filterRawObjects];
}
return _filteredObjects;
}
- (void)modelsDidLoad:(NSArray*)models {
[models retain];
[_objects release];
_objects = nil;
[_filteredObjects release];
_filteredObjects = nil;
if (_searchText) {
results = [self search:_searchText inCollection:results];
}
_objects = models;
[self filterRawObjects];
_isLoaded = YES;
if (self.sortSelector) {
results = [results sortedArrayUsingSelector:self.sortSelector];
} else if (self.sortDescriptors) {
results = [results sortedArrayUsingDescriptors:self.sortDescriptors];
}
[self didFinishLoad];
}
- (void)setPredicate:(NSPredicate*)predicate {
[_predicate release];
_predicate = nil;
_predicate = [predicate retain];
return results;
[_filteredObjects release];
_filteredObjects = nil;
}
- (void)setSortDescriptors:(NSArray*)sortDescriptors {
[_sortDescriptors release];
_sortDescriptors = nil;
_sortDescriptors = [sortDescriptors retain];
[_filteredObjects release];
_filteredObjects = nil;
}
- (void)setSortSelector:(SEL)sortSelector {
_sortSelector = nil;
_sortSelector = sortSelector;
[_filteredObjects release];
_filteredObjects = nil;
}
@end

View File

@@ -1,108 +0,0 @@
//
// RKRequestModel.h
// RestKit
//
// Created by Jeff Arena on 4/26/10.
// Copyright 2010 RestKit. All rights reserved.
//
#import "../RestKit.h"
#import "../CoreData/RKManagedObject.h"
/**
* Lifecycle events for RKRequestModel
*
* Modeled off of RKRequestDelegate (and therefore TTURLRequest)
*/
@protocol RKRequestModelDelegate
@optional
- (void)rkModelDidStartLoad;
- (void)rkModelDidFinishLoad;
- (void)rkModelDidFailLoadWithError:(NSError*)error;
- (void)rkModelDidCancelLoad;
- (void)rkModelDidLoad;
@end
/**
* Generic class for loading a remote model using a RestKit request
*/
@interface RKRequestModel : NSObject <RKObjectLoaderDelegate, RKRequestDelegate> {
NSArray *_objects;
BOOL _loaded;
NSString* _resourcePath;
NSDictionary* _params;
RKRequestMethod _method;
id _delegate;
RKObjectLoader* _objectLoader;
Class _objectClass;
NSString* _keyPath;
NSTimeInterval _refreshRate;
}
/**
* Domain objects loaded via this model
*/
@property (nonatomic, readonly) NSArray *objects;
@property (readonly) BOOL loaded;
@property (nonatomic, readonly) NSString* resourcePath;
/**
* Any parameters POSTed with the request
*/
@property (nonatomic, readonly) NSDictionary* params;
@property (nonatomic, readonly) RKObjectLoader* objectLoader;
/**
* The HTTP method to load the models with. Defaults to RKRequestMethodGET
*/
@property (nonatomic, assign) RKRequestMethod method;
@property (assign) NSTimeInterval refreshRate;
/**
* Init methods for creating new models
*/
- (id)initWithResourcePath:(NSString*)resourcePath delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass keyPath:(NSString*)keyPath delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass delegate:(id)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass keyPath:(NSString*)keyPath delegate:(id)delegate;
/**
* Clear the last loaded time for the model
*/
- (void)clearLoadedTime;
/*
* Save the last loaded time for the model
*/
- (void)saveLoadedTime;
/**
* Get the last loaded time for the model
*/
- (NSDate*)loadedTime;
/**
* Invoked after a remote request has completed and model objects have been
* built from the response. Subclasses must invoke super to complete the load operation
*/
- (void)modelsDidLoad:(NSArray*)models;
- (void)reset;
- (void)load;
- (void)loadFromObjectCache;
@end

View File

@@ -1,310 +0,0 @@
//
// RKRequestModel.m
// RestKit
//
// Created by Jeff Arena on 4/26/10.
// Copyright 2010 RestKit. All rights reserved.
//
#import "RKRequestModel.h"
#import "RKManagedObjectStore.h"
#import <Three20/Three20.h>
@implementation RKRequestModel
@synthesize objects = _objects;
@synthesize loaded = _loaded;
@synthesize resourcePath = _resourcePath;
@synthesize params = _params;
@synthesize objectLoader = _objectLoader;
@synthesize method = _method;
@synthesize refreshRate = _refreshRate;
- (id)initWithResourcePath:(NSString*)resourcePath delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
}
[self loadFromObjectCache];
return self;
}
/**
* TODO: These initializers have to set the ivars so the state is configured before loadFromObjectCache is triggered.
* Do NOT DRY these up by adding dependencies or loadFromObjectCache will fire too early.
*
* WARNING
*/
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
}
[self loadFromObjectCache];
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
_objectClass = [klass retain];
}
[self loadFromObjectCache];
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass keyPath:(NSString*)keyPath delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
_objectClass = [klass retain];
_keyPath = [keyPath retain];
}
[self loadFromObjectCache];
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
_method = method;
}
[self loadFromObjectCache];
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
_objectClass = [klass retain];
_method = method;
}
[self loadFromObjectCache];
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass keyPath:(NSString*)keyPath delegate:(id)delegate {
if (self = [self init]) {
_resourcePath = [resourcePath retain];
_delegate = [delegate retain];
_params = [params retain];
_objectClass = [klass retain];
_keyPath = [keyPath retain];
_method = method;
}
[self loadFromObjectCache];
return self;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// NSObject
- (id)init {
if (self = [super init]) {
_method = RKRequestMethodGET;
_refreshRate = NSTimeIntervalSince1970; // Essentially, default to never
}
return self;
}
- (void)dealloc {
[_delegate release];
[_objectLoader.request cancel];
[_objectLoader release];
[_params release];
[_objects release];
[_objectClass release];
[_keyPath release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RKRequestDelegate
- (void)requestDidFinishLoad:(RKRequest*)request {
[self saveLoadedTime];
if ([_delegate respondsToSelector:@selector(rkModelDidFinishLoad)]) {
[_delegate rkModelDidFinishLoad];
}
}
//// TODO: I get replaced...
- (void)request:(RKRequest*)request didFailLoadWithError:(NSError*)error {
if ([_delegate respondsToSelector:@selector(rkModelDidFailLoadWithError:)]) {
[_delegate rkModelDidFailLoadWithError:error];
}
}
- (void)requestDidCancelLoad:(RKRequest*)request {
if ([_delegate respondsToSelector:@selector(rkModelDidCancelLoad)]) {
[_delegate rkModelDidCancelLoad];
}
}
- (BOOL)needsRefresh {
NSDate* loadedTime = self.loadedTime;
BOOL outdated = NO;
if (loadedTime) {
outdated = -[loadedTime timeIntervalSinceNow] > _refreshRate;
} else {
[self saveLoadedTime];
}
return outdated;
}
- (void)clearLoadedTime {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:_resourcePath];
}
- (void)saveLoadedTime {
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:_resourcePath];
}
- (NSDate*)loadedTime {
return [[NSUserDefaults standardUserDefaults] objectForKey:_resourcePath];
}
- (void)loadFromObjectCache {
RKManagedObjectStore* store = [RKObjectManager sharedManager].objectStore;
NSArray* cachedObjects = nil;
if (store.managedObjectCache) {
cachedObjects = [RKManagedObject objectsWithFetchRequests:[store.managedObjectCache fetchRequestsForResourcePath:self.resourcePath]];
if (cachedObjects && [cachedObjects count] > 0) {
if ([_delegate respondsToSelector:@selector(rkModelDidStartLoad)]) {
[_delegate rkModelDidStartLoad];
}
_objects = [cachedObjects retain];
_loaded = YES;
if ([_delegate respondsToSelector:@selector(rkModelDidLoad)]) {
[_delegate rkModelDidLoad];
}
} else {
[self load];
}
}
if ([self needsRefresh]) {
[self load];
}
}
- (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:self
cancelButtonTitle:TTLocalizedString(@"OK", @"")
otherButtonTitles:TTLocalizedString(@"Go Offline", @""), nil] autorelease];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Go Offline button
if (1 == buttonIndex) {
[[RKObjectManager sharedManager] goOffline];
}
}
- (void)modelsDidLoad:(NSArray*)models {
[models retain];
[_objects release];
_objects = models;
_loaded = YES;
// NOTE: You must finish load after clearing the loadingRequest and setting the loaded flag
if ([_delegate respondsToSelector:@selector(rkModelDidLoad)]) {
[_delegate rkModelDidLoad];
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RKModelLoaderDelegate
// This callback is invoked after the request has been fully serviced. Finish the load here.
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
[objectLoader release];
_objectLoader = nil;
[self modelsDidLoad:objects];
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
[objectLoader release];
_objectLoader = nil;
if ([self errorWarrantsOptionToGoOffline:error]) {
[self showAlertWithOptionToGoOfflineForError:error];
} else {
[_delegate didFailLoadWithError:error];
}
}
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader*)objectLoader {
[objectLoader release];
_objectLoader = nil;
[_delegate didFailLoadWithError:nil];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// public
- (void)reset {
[self clearLoadedTime];
}
- (void)load {
if ([[RKObjectManager sharedManager] isOnline]) {
if ([_delegate respondsToSelector:@selector(rkModelDidStartLoad)]) {
[_delegate rkModelDidStartLoad];
}
_objectLoader = [[[RKObjectManager sharedManager] objectLoaderWithResourcePath:_resourcePath delegate:self] retain];
_objectLoader.method = _method;
_objectLoader.objectClass = _objectClass;
_objectLoader.keyPath = _keyPath;
_objectLoader.params = _params;
[_objectLoader send];
}
}
@end

View File

@@ -7,27 +7,86 @@
//
#import <Three20/Three20.h>
#import "RKRequestModel.h"
#import "../RestKit.h"
/**
* Generic class for loading a remote model using a RestKit request and supplying the model to a
* TTListDataSource subclass
*/
@interface RKRequestTTModel : TTModel <RKRequestModelDelegate> {
RKRequestModel* _model;
@interface RKRequestTTModel : TTModel <RKObjectLoaderDelegate> {
NSArray *_objects;
BOOL _isLoaded;
BOOL _isLoading;
BOOL _cacheLoaded;
NSString* _resourcePath;
NSDictionary* _params;
RKRequestMethod _method;
Class _objectClass;
NSString* _keyPath;
NSTimeInterval _refreshRate;
}
@property (nonatomic, readonly) RKRequestModel* model;
/**
* 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;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass;
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass keyPath:(NSString*)keyPath;
- (NSArray*)objects;
/**
* The NSDate representing the first time the app was run. This defaultLoadedTime
* is used in comparison with refreshRate in cases where a resourcePath-specific
* loadedTime has yet to be established for a given resourcePath.
*/
+ (NSDate*)defaultLoadedTime;
/**
* App-level default refreshRate used in determining when to refresh a given model.
* Defaults to NSTimeIntervalSince1970, which essentially means all app models
* will never refresh.
*/
+ (NSTimeInterval)defaultRefreshRate;
/**
* Setter for defaultRefreshRate, which allows one to set an app-wide refreshRate
* for all models, as opposed to having to set the refreshRate on every instantiation
* of RKRequestTTModel.
*/
+ (void)setDefaultRefreshRate:(NSTimeInterval)newDefaultRefreshRate;
@end

View File

@@ -7,56 +7,81 @@
//
#import "RKRequestTTModel.h"
#import "RKManagedObjectStore.h"
#import "../Network/Network.h"
static NSTimeInterval defaultRefreshRate = NSTimeIntervalSince1970;
static NSString* const kDefaultLoadedTimeKey = @"RKRequestTTModelDefaultLoadedTimeKey";
@interface RKRequestTTModel (Private)
- (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
@synthesize model = _model;
@synthesize objects = _objects;
@synthesize resourcePath = _resourcePath;
@synthesize params = _params;
@synthesize method = _method;
@synthesize refreshRate = _refreshRate;
+ (NSDate*)defaultLoadedTime {
NSDate* defaultLoadedTime = [[NSUserDefaults standardUserDefaults] objectForKey:kDefaultLoadedTimeKey];
if (defaultLoadedTime == nil) {
defaultLoadedTime = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:defaultLoadedTime forKey:kDefaultLoadedTimeKey];
}
return defaultLoadedTime;
}
+ (NSTimeInterval)defaultRefreshRate {
return defaultRefreshRate;
}
+ (void)setDefaultRefreshRate:(NSTimeInterval)newDefaultRefreshRate {
defaultRefreshRate = defaultRefreshRate;
}
- (id)initWithResourcePath:(NSString*)resourcePath {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath delegate:self];
_resourcePath = [resourcePath retain];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params delegate:self];
}
if (self = [self initWithResourcePath:resourcePath]) {
self.params = [params retain];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass{
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params objectClass:klass delegate:self];
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass {
if (self = [self initWithResourcePath:resourcePath params:params]) {
_objectClass = [klass retain];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params objectClass:(Class)klass keyPath:(NSString*)keyPath {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params objectClass:klass keyPath:keyPath delegate:self];
if (self = [self initWithResourcePath:resourcePath params:params objectClass:klass]) {
_keyPath = [keyPath retain];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params method:method delegate:self];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params method:method objectClass:klass delegate:self];
}
return self;
}
- (id)initWithResourcePath:(NSString*)resourcePath params:(NSDictionary*)params method:(RKRequestMethod)method objectClass:(Class)klass keyPath:(NSString*)keyPath {
if (self = [self init]) {
_model = [[RKRequestModel alloc] initWithResourcePath:resourcePath params:params method:method objectClass:klass keyPath:keyPath delegate:self];
if (self = [self initWithResourcePath:resourcePath params:params]) {
_method = method;
}
return self;
}
@@ -66,14 +91,29 @@
- (id)init {
if (self = [super init]) {
_model = nil;
self.method = RKRequestMethodGET;
self.refreshRate = [RKRequestTTModel defaultRefreshRate];
self.params = nil;
_cacheLoaded = NO;
_objects = nil;
_isLoaded = NO;
_isLoading = NO;
_resourcePath = nil;
}
return self;
}
- (void)dealloc {
[_model release];
_model = nil;
[[RKRequestQueue sharedQueue] cancelRequestsWithDelegate:self];
[_objects release];
_objects = nil;
[_resourcePath release];
_resourcePath = nil;
[_objectClass release];
_objectClass = nil;
[_keyPath release];
_keyPath = nil;
self.params = nil;
[super dealloc];
}
@@ -81,11 +121,11 @@
// TTModel
- (BOOL)isLoaded {
return _model.loaded;
return _isLoaded;
}
- (BOOL)isLoading {
return nil != _model.objectLoader;
return _isLoading;
}
- (BOOL)isLoadingMore {
@@ -93,52 +133,138 @@
}
- (BOOL)isOutdated {
return NO;
return (![self isLoading] && (-[self.loadedTime timeIntervalSinceNow] > _refreshRate));
}
- (void)cancel {
if (_model && _model.objectLoader.request) {
[_model.objectLoader.request cancel];
}
[[RKRequestQueue sharedQueue] cancelRequestsWithDelegate:self];
[self didCancelLoad];
}
- (void)invalidate:(BOOL)erase {
// TODO: Note sure how to handle erase...
[_model clearLoadedTime];
}
- (void)reset {
[_model reset];
[self clearLoadedTime];
}
- (void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more {
[_model load];
[self load];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// RKRequestModelDelegate
- (void)rkModelDidStartLoad {
[self didStartLoad];
- (NSDate*)loadedTime {
NSDate* loadedTime = [[NSUserDefaults standardUserDefaults] objectForKey:_resourcePath];
if (loadedTime == nil) {
return [RKRequestTTModel defaultLoadedTime];
}
return loadedTime;
}
- (void)rkModelDidFailLoadWithError:(NSError*)error {
#pragma mark RKModelLoaderDelegate
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
_isLoading = NO;
[self saveLoadedTime];
[self modelsDidLoad:objects];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
_isLoading = NO;
[self didFailLoadWithError:error];
// if ([self errorWarrantsOptionToGoOffline:error]) {
// [self showAlertWithOptionToGoOfflineForError:error];
// }
}
- (void)rkModelDidCancelLoad {
[self didCancelLoad];
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader*)objectLoader {
_isLoading = NO;
// TODO: Passing a nil error here does nothing for Three20. Need to construct our
// own error here to make Three20 happy??
[self didFailLoadWithError:nil];
}
- (void)rkModelDidLoad {
#pragma mark RKRequestTTModel (Private)
- (void)clearLoadedTime {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:_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:self
cancelButtonTitle:TTLocalizedString(@"OK", @"")
otherButtonTitles:TTLocalizedString(@"Go Offline", @""), nil] autorelease];
[alert show];
}
- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Go Offline button
if (1 == buttonIndex) {
[[RKObjectManager sharedManager] goOffline];
}
}
- (void)modelsDidLoad:(NSArray*)models {
[models retain];
[_objects release];
_objects = nil;
_objects = models;
_isLoaded = YES;
[self didFinishLoad];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// public
- (NSArray*)objects {
return _model.objects;
- (void)load {
RKManagedObjectStore* store = [RKObjectManager sharedManager].objectStore;
NSArray* cacheFetchRequests = nil;
NSArray* cachedObjects = nil;
if (store.managedObjectCache) {
cacheFetchRequests = [store.managedObjectCache fetchRequestsForResourcePath:self.resourcePath];
cachedObjects = [RKManagedObject objectsWithFetchRequests:cacheFetchRequests];
}
if (!store.managedObjectCache || !cacheFetchRequests || _cacheLoaded ||
([cachedObjects count] == 0 && [[RKObjectManager sharedManager] isOnline])) {
RKObjectLoader* objectLoader = [[[RKObjectManager sharedManager] objectLoaderWithResourcePath:_resourcePath delegate:self] retain];
objectLoader.method = self.method;
objectLoader.objectClass = _objectClass;
objectLoader.keyPath = _keyPath;
objectLoader.params = self.params;
_isLoading = YES;
[self didStartLoad];
[objectLoader send];
} else if (cacheFetchRequests && !_cacheLoaded) {
_cacheLoaded = YES;
[self modelsDidLoad:cachedObjects];
}
}
@end

View File

@@ -6,6 +6,5 @@
// Copyright 2010 Two Toasters. All rights reserved.
//
#import "RKRequestModel.h"
#import "RKRequestTTModel.h"
#import "RKRequestFilterableTTModel.h"