Merge branch 'master' of github.com:twotoasters/RestKit

This commit is contained in:
Jeremy Ellison
2011-01-06 15:48:31 -05:00
7 changed files with 161 additions and 93 deletions

View File

@@ -47,6 +47,9 @@ NSString* RKMakeURLPath(NSString* resourcePath);
NSString* _password;
NSMutableDictionary* _HTTPHeaders;
RKReachabilityObserver* _baseURLReachabilityObserver;
NSString* _serviceUnavailableAlertTitle;
NSString* _serviceUnavailableAlertMessage;
BOOL _serviceUnavailableAlertEnabled;
}
/**
@@ -75,6 +78,27 @@ NSString* RKMakeURLPath(NSString* resourcePath);
*/
@property(nonatomic, readonly) RKReachabilityObserver* baseURLReachabilityObserver;
/**
* The title to use in the UIAlertView shown when a request encounters a
* ServiceUnavailable (503) response.
* If not provided, the default is: "Service Unavailable"
*/
@property(nonatomic, retain) NSString* serviceUnavailableAlertTitle;
/**
* The message to use in the UIAlertView shown when a request encounters a
* ServiceUnavailable (503) response.
* If not provided, the default is: "The remote resource is unavailable. Please try again later."
*/
@property(nonatomic, retain) NSString* serviceUnavailableAlertMessage;
/**
* Flag that determines whether the Service Unavailable alert is shown in response
* to a ServiceUnavailable (503) response.
* Defaults to NO.
*/
@property(nonatomic, assign) BOOL serviceUnavailableAlertEnabled;
/**
* Return the configured singleton instance of the Rest client
*/

View File

@@ -36,6 +36,9 @@ NSString* RKMakeURLPath(NSString* resourcePath) {
@synthesize password = _password;
@synthesize HTTPHeaders = _HTTPHeaders;
@synthesize baseURLReachabilityObserver = _baseURLReachabilityObserver;
@synthesize serviceUnavailableAlertTitle = _serviceUnavailableAlertTitle;
@synthesize serviceUnavailableAlertMessage = _serviceUnavailableAlertMessage;
@synthesize serviceUnavailableAlertEnabled = _serviceUnavailableAlertEnabled;
+ (RKClient*)sharedClient {
return sharedClient;
@@ -78,15 +81,20 @@ NSString* RKMakeURLPath(NSString* resourcePath) {
- (id)init {
if (self = [super init]) {
_HTTPHeaders = [[NSMutableDictionary alloc] init];
self.serviceUnavailableAlertEnabled = NO;
self.serviceUnavailableAlertTitle = NSLocalizedString(@"Service Unavailable", nil);
self.serviceUnavailableAlertMessage = NSLocalizedString(@"The remote resource is unavailable. Please try again later.", nil);
}
return self;
}
- (void)dealloc {
[_baseURL release];
[_username release];
[_password release];
self.baseURL = nil;
self.username = nil;
self.password = nil;
self.serviceUnavailableAlertTitle = nil;
self.serviceUnavailableAlertMessage = nil;
[_HTTPHeaders release];
[super dealloc];
}

View File

@@ -14,6 +14,7 @@
#import "RKClient.h"
#import "../Support/Support.h"
#import "RKURL.h"
#import <UIKit/UIKit.h>
@implementation RKRequest
@@ -64,54 +65,54 @@
[super dealloc];
}
- (void)setRequestBody {
if (_params) {
// Prefer the use of a stream over a raw body
if ([_params respondsToSelector:@selector(HTTPBodyStream)]) {
[_URLRequest setHTTPBodyStream:[_params HTTPBodyStream]];
} else {
[_URLRequest setHTTPBody:[_params HTTPBody]];
}
}
}
- (void)addHeadersToRequest {
NSString* header;
for (header in _additionalHTTPHeaders) {
[_URLRequest setValue:[_additionalHTTPHeaders valueForKey:header] forHTTPHeaderField:header];
}
if (_params != nil) {
// Temporarily support older RKRequestSerializable implementations
if ([_params respondsToSelector:@selector(HTTPHeaderValueForContentType)]) {
[_URLRequest setValue:[_params HTTPHeaderValueForContentType] forHTTPHeaderField:@"Content-Type"];
} else if ([_params respondsToSelector:@selector(ContentTypeHTTPHeader)]) {
[_URLRequest setValue:[_params performSelector:@selector(ContentTypeHTTPHeader)] forHTTPHeaderField:@"Content-Type"];
}
}
if ([_params respondsToSelector:@selector(HTTPHeaderValueForContentLength)]) {
[_URLRequest setValue:[NSString stringWithFormat:@"%d", [_params HTTPHeaderValueForContentLength]] forHTTPHeaderField:@"Content-Length"];
}
}
if (_username != nil) {
// Add authentication headers so we don't have to deal with an extra cycle for each message requiring basic auth.
CFHTTPMessageRef dummyRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self HTTPMethod], (CFURLRef)[self URL], kCFHTTPVersion1_1);
CFHTTPMessageAddAuthentication(dummyRequest, nil, (CFStringRef)_username, (CFStringRef)_password, kCFHTTPAuthenticationSchemeBasic, FALSE);
CFStringRef authorizationString = CFHTTPMessageCopyHeaderFieldValue(dummyRequest, CFSTR("Authorization"));
[_URLRequest setValue:(NSString *)authorizationString forHTTPHeaderField:@"Authorization"];
CFRelease(dummyRequest);
CFRelease(authorizationString);
}
NSLog(@"Headers: %@", [_URLRequest allHTTPHeaderFields]);
}
- (void)setMethod:(RKRequestMethod)method {
_method = method;
// Setup the NSURLRequest. The request must be prepared right before dispatching
- (void)prepareURLRequest {
[_URLRequest setHTTPMethod:[self HTTPMethod]];
}
- (void)setParams:(NSObject<RKRequestSerializable>*)params {
[params retain];
[_params release];
_params = params;
if (params && ![self isGET]) {
// Prefer the use of a stream over a raw body
if ([_params respondsToSelector:@selector(HTTPBodyStream)]) {
[_URLRequest setHTTPBodyStream:[_params HTTPBodyStream]];
} else {
[_URLRequest setHTTPBody:[_params HTTPBody]];
}
}
[self setRequestBody];
[self addHeadersToRequest];
}
- (NSString*)HTTPMethod {
@@ -140,14 +141,14 @@
- (void)fireAsynchronousRequest {
if ([[RKClient sharedClient] isNetworkAvailable]) {
[self addHeadersToRequest];
[self prepareURLRequest];
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];
@@ -156,7 +157,7 @@
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
errorMessage, NSLocalizedDescriptionKey,
nil];
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
NSError* error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKRequestBaseURLOfflineError userInfo:userInfo];
[self didFailLoadWithError:error];
}
}
@@ -166,16 +167,16 @@
NSError* error = nil;
NSData* payload = nil;
RKResponse* response = nil;
if ([[RKClient sharedClient] isNetworkAvailable]) {
[self addHeadersToRequest];
[self prepareURLRequest];
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];
@@ -186,11 +187,11 @@
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;
}
@@ -203,28 +204,39 @@
- (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",
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL", receivedAt, @"receivedAt", error, @"error", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestFailedWithErrorNotification object:self userInfo:userInfo];
[[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];
[[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:response userInfo:userInfo];
if ([response isServiceUnavailable] && [[RKClient sharedClient] serviceUnavailableAlertEnabled]) {
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:[[RKClient sharedClient] serviceUnavailableAlertTitle]
message:[[RKClient sharedClient] serviceUnavailableAlertMessage]
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OK", nil)
otherButtonTitles:nil];
[alertView show];
[alertView release];
}
}
- (BOOL)isGET {

View File

@@ -40,14 +40,14 @@ static const NSInteger kMaxConcurrentLoads = 5;
_requests = [[NSMutableArray alloc] init];
_suspended = NO;
_totalLoading = 0;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKResponseReceivedNotification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKResponseReceivedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKRequestFailedWithErrorNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(responseDidLoad:)
name:kRKRequestFailedWithErrorNotification
object:nil];
}
return self;
}
@@ -61,10 +61,10 @@ static const NSInteger kMaxConcurrentLoads = 5;
- (void)loadNextInQueueDelayed {
if (!_queueTimer) {
_queueTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushDelay
_queueTimer = [NSTimer scheduledTimerWithTimeInterval:kFlushDelay
target:self
selector:@selector(loadNextInQueue)
userInfo:nil
selector:@selector(loadNextInQueue)
userInfo:nil
repeats:NO];
}
}
@@ -75,21 +75,21 @@ static const NSInteger kMaxConcurrentLoads = 5;
- (void)loadNextInQueue {
// This makes sure that the Request Queue does not fire off any requests until the Reachability state has been determined.
// This prevents the request queue from
if ([[[RKClient client] baseURLReachabilityObserver] networkStatus] == RKReachabilityIndeterminate) {
// This prevents the request queue from
if ([[[RKClient sharedClient] baseURLReachabilityObserver] networkStatus] == RKReachabilityIndeterminate) {
[self loadNextInQueueDelayed];
return;
}
_queueTimer = nil;
for (RKRequest* request in _requests) {
if (![request isLoading] && ![request isLoaded] && _totalLoading < kMaxConcurrentLoads) {
++_totalLoading;
[self dispatchRequest:request];
}
}
if (_requests.count && !_suspended) {
[self loadNextInQueueDelayed];
}
@@ -97,7 +97,7 @@ static const NSInteger kMaxConcurrentLoads = 5;
- (void)setSuspended:(BOOL)isSuspended {
_suspended = isSuspended;
if (!_suspended) {
[self loadNextInQueue];
} else if (_queueTimer) {
@@ -115,10 +115,10 @@ static const NSInteger kMaxConcurrentLoads = 5;
if ([_requests containsObject:request] && ![request isLoaded]) {
[request cancel];
request.delegate = nil;
[_requests removeObject:request];
_totalLoading--;
if (loadNext) {
[self loadNextInQueue];
}
@@ -156,14 +156,14 @@ static const NSInteger kMaxConcurrentLoads = 5;
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];
}
}

View File

@@ -168,6 +168,11 @@
*/
- (BOOL)isEmpty;
/**
* Indicates an HTTP response code of 503
*/
- (BOOL)isServiceUnavailable;
/**
* Returns the value of 'Content-Type' HTTP header
*/

View File

@@ -203,6 +203,10 @@
return ([self statusCode] == 201 || [self statusCode] == 204 || [self statusCode] == 304);
}
- (BOOL)isServiceUnavailable {
return ([self statusCode] == 503);
}
- (NSString*)contentType {
return ([[self allHeaderFields] objectForKey:@"Content-Type"]);
}

View File

@@ -14,6 +14,7 @@
#import "RKManagedObject.h"
#import "RKURL.h"
#import "RKNotifications.h"
#import <UIKit/UIKit.h>
@implementation RKObjectLoader
@@ -29,7 +30,7 @@
_mapper = [mapper retain];
self.managedObjectStore = nil;
_targetObjectID = nil;
[[RKClient sharedClient] setupRequest:self];
}
return self;
@@ -45,7 +46,7 @@
[_targetObject release];
_targetObject = nil;
[_targetObjectID release];
_targetObjectID = nil;
_targetObjectID = nil;
self.managedObjectStore = nil;
[super dealloc];
}
@@ -54,10 +55,10 @@
[_targetObject release];
_targetObject = nil;
_targetObject = [targetObject retain];
[_targetObjectID release];
_targetObjectID = nil;
if ([targetObject isKindOfClass:[NSManagedObject class]]) {
_targetObjectID = [[(NSManagedObject*)targetObject objectID] retain];
}
@@ -68,10 +69,10 @@
- (void)responseProcessingSuccessful:(BOOL)successful withError:(NSError*)error {
_isLoading = NO;
NSDate* receivedAt = [NSDate date];
if (successful) {
_isLoaded = YES;
_isLoaded = YES;
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL",
receivedAt, @"receivedAt",
@@ -80,7 +81,7 @@
object:_response
userInfo:userInfo];
} else {
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[self HTTPMethod], @"HTTPMethod",
[self URL], @"URL",
receivedAt, @"receivedAt",
error, @"error",
@@ -94,16 +95,30 @@
- (BOOL)encounteredErrorWhileProcessingRequest:(RKResponse*)response {
if ([response isFailure]) {
[(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]) {
error = [_mapper parseErrorFromString:[response bodyAsString]];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:error];
} else if ([response isServiceUnavailable] && [[RKClient sharedClient] serviceUnavailableAlertEnabled]) {
if ([_delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) {
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoaderDidLoadUnexpectedResponse:self];
}
UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:[[RKClient sharedClient] serviceUnavailableAlertTitle]
message:[[RKClient sharedClient] serviceUnavailableAlertMessage]
delegate:nil
cancelButtonTitle:NSLocalizedString(@"OK", nil)
otherButtonTitles:nil];
[alertView show];
[alertView release];
} else {
// TODO: We've likely run into a maintenance page here. Consider adding the ability
// to put the stack into offline mode in response...
@@ -111,9 +126,9 @@
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoaderDidLoadUnexpectedResponse:self];
}
}
[self responseProcessingSuccessful:NO withError:error];
return YES;
}
return NO;
@@ -122,7 +137,7 @@
- (void)informDelegateOfObjectLoadWithInfoDictionary:(NSDictionary*)dictionary {
NSArray* models = [dictionary objectForKey:@"models"];
[dictionary release];
// NOTE: The models dictionary may contain NSManagedObjectID's from persistent objects
// that were model mapped on a background thread. We look up the objects by ID and then
// notify the delegate that the operation has completed.
@@ -134,32 +149,32 @@
[objects addObject:object];
}
}
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didLoadObjects:[NSArray arrayWithArray:objects]];
[self responseProcessingSuccessful:YES withError:nil];
}
- (void)informDelegateOfObjectLoadErrorWithInfoDictionary:(NSDictionary*)dictionary {
NSError* error = [dictionary objectForKey:@"error"];
[dictionary release];
NSLog(@"[RestKit] RKObjectLoader: Error saving managed object context: error=%@ userInfo=%@", error, error.userInfo);
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[error localizedDescription], NSLocalizedDescriptionKey,
nil];
nil];
NSError *rkError = [NSError errorWithDomain:RKRestKitErrorDomain code:RKObjectLoaderRemoteSystemError userInfo:userInfo];
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoader:self didFailWithError:rkError];
[self responseProcessingSuccessful:NO withError:rkError];
}
- (void)processLoadModelsInBackground:(RKResponse *)response {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
RKManagedObjectStore* objectStore = self.managedObjectStore;
/**
* If this loader is bound to a particular object, then we map
* the results back into the instance. This is used for loading and updating
@@ -188,12 +203,12 @@
// so that in the event result is nil, then we get empty array instead of exception for trying to insert nil.
results = [NSArray arrayWithObjects:result, nil];
}
if (objectStore && [objectStore managedObjectCache]) {
if ([self.URL isKindOfClass:[RKURL class]]) {
RKURL* rkURL = (RKURL*)self.URL;
NSArray* fetchRequests = [[objectStore managedObjectCache] fetchRequestsForResourcePath:rkURL.resourcePath];
NSArray* cachedObjects = [RKManagedObject objectsWithFetchRequests:fetchRequests];
NSArray* cachedObjects = [RKManagedObject objectsWithFetchRequests:fetchRequests];
for (id object in cachedObjects) {
if ([object isKindOfClass:[RKManagedObject class]]) {
if (NO == [results containsObject:object]) {
@@ -204,15 +219,15 @@
}
}
}
// Before looking up NSManagedObjectIDs, need to save to ensure we do not have
// temporary IDs for new objects prior to handing the objectIDs across threads
NSError* error = [objectStore save];
if (nil != error) {
NSDictionary* infoDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:response, @"response", error, @"error", nil] retain];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadErrorWithInfoDictionary:) withObject:infoDictionary waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadErrorWithInfoDictionary:) withObject:infoDictionary waitUntilDone:YES];
} else {
// NOTE: Passing Core Data objects across threads is not safe.
// 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.
// The object ID's will be deserialized back into objects on the main thread before the delegate is called back
NSMutableArray* models = [NSMutableArray arrayWithCapacity:[results count]];
@@ -220,10 +235,10 @@
if ([object isKindOfClass:[NSManagedObject class]]) {
[models addObject:[(NSManagedObject*)object objectID]];
} else {
[models addObject:object];
[models addObject:object];
}
}
}
NSDictionary* infoDictionary = [[NSDictionary dictionaryWithObjectsAndKeys:response, @"response", models, @"models", nil] retain];
[self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadWithInfoDictionary:) withObject:infoDictionary waitUntilDone:YES];
}
@@ -235,19 +250,19 @@
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]) {
@@ -256,7 +271,7 @@
NSLog(@"Encountered unexpected response code: %d (MIME Type: %@)", response.statusCode, response.MIMEType);
if ([_delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) {
[(NSObject<RKObjectLoaderDelegate>*)_delegate objectLoaderDidLoadUnexpectedResponse:self];
}
}
[self responseProcessingSuccessful:NO withError:nil];
}
}