[#11477593] Implemented background request policies and Specs. This provides functionality for continuing a request in the background using an iOS background task.

Introduces four modes for handling background requests:
* RKRequestBackgroundPolicyNone - The default behavior replicating pre-background behavior. No special action is taken with regards to backgrounding.
* RKRequestBackgroundPolicyCancel - On transition to the background, requests with this policy set will be cancelled automatically and the delegate informed.
* RKRequestBackgroundPolicyContinue - Requests with this policy will be continued in the background after the app has been transitioned.
* RKRequestBackgroundPolicyRequeue - Requests with this policy will be cancelled and then immediately placed onto the queue for processing the next time the app is returned to the foreground.
This commit is contained in:
Blake Watters
2011-03-29 13:23:17 -04:00
parent 334db23e9d
commit a648d26460
16 changed files with 364 additions and 93 deletions

View File

@@ -19,7 +19,8 @@
@implementation RKRequest
@synthesize URL = _URL, URLRequest = _URLRequest, delegate = _delegate, additionalHTTPHeaders = _additionalHTTPHeaders,
params = _params, userData = _userData, username = _username, password = _password, method = _method;
params = _params, userData = _userData, username = _username, password = _password, method = _method,
backgroundPolicy = _backgroundPolicy, backgroundTaskIdentifier = _backgroundTaskIdentifier;
+ (RKRequest*)requestWithURL:(NSURL*)URL delegate:(id)delegate {
return [[[RKRequest alloc] initWithURL:URL delegate:delegate] autorelease];
@@ -34,37 +35,68 @@
_isLoading = NO;
_isLoaded = NO;
}
return self;
}
- (id)initWithURL:(NSURL*)URL delegate:(id)delegate {
self = [self initWithURL:URL];
if (self) {
_delegate = delegate;
_delegate = delegate;
}
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];
- (id)init {
self = [super init];
if (self) {
_backgroundPolicy = RKRequestBackgroundPolicyNone;
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
return self;
}
- (void)cleanupBackgroundTask {
if (UIBackgroundTaskInvalid == self.backgroundTaskIdentifier) {
return;
}
NSLog(@"Cleaning up background task...");
UIApplication* app = [UIApplication sharedApplication];
if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
[app endBackgroundTask:_backgroundTaskIdentifier];
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
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;
// Cleanup a background task if there is any
[self cleanupBackgroundTask];
[super dealloc];
}
- (void)setRequestBody {
@@ -104,6 +136,17 @@
[self addHeadersToRequest];
}
- (void)cancelAndInformDelegate:(BOOL)informDelegate {
[_connection cancel];
[_connection release];
_connection = nil;
_isLoading = NO;
if (informDelegate && [_delegate respondsToSelector:@selector(requestDidCancelLoad:)]) {
[_delegate requestDidCancelLoad:self];
}
}
- (NSString*)HTTPMethod {
switch (_method) {
case RKRequestMethodGET:
@@ -129,18 +172,54 @@
}
- (void)fireAsynchronousRequest {
if ([[RKClient sharedClient] isNetworkAvailable]) {
[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:RKRequestSentNotification object:self userInfo:userInfo];
[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];
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
_isLoading = YES;
RKResponse* response = [[[RKResponse alloc] initWithRequest:self] autorelease];
_connection = [[NSURLConnection connectionWithRequest:_URLRequest delegate:response] retain];
}
_isLoading = YES;
RKResponse* response = [[[RKResponse alloc] initWithRequest:self] autorelease];
_connection = [[NSURLConnection connectionWithRequest:_URLRequest delegate:response] retain];
- (BOOL)shouldDispatchRequest {
return [RKClient sharedClient] == nil || [[RKClient sharedClient] isNetworkAvailable];
}
- (void)sendAsynchronously {
if ([self shouldDispatchRequest]) {
// Background Request Policy support
UIApplication* app = [UIApplication sharedApplication];
if (self.backgroundPolicy == RKRequestBackgroundPolicyNone ||
NO == [app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
// No support for background (iOS 3.x) or the policy is none -- just fire the request
[self fireAsynchronousRequest];
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyCancel || self.backgroundPolicy == RKRequestBackgroundPolicyRequeue) {
// For cancel or requeue behaviors, we watch for background transition notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appDidEnterBackgroundNotification:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyContinue) {
NSLog(@"Beginning background task to perform processing...");
// Fork a background task for continueing a long-running request
_backgroundTaskIdentifier = [app beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"Background request time expired, canceling request.");
[self cancelAndInformDelegate:NO];
[self cleanupBackgroundTask];
if ([_delegate respondsToSelector:@selector(requestDidTimeout:)]) {
[_delegate requestDidTimeout:self];
}
}];
// Start the potentially long-running request
[self fireAsynchronousRequest];
}
} else {
NSString* errorMessage = [NSString stringWithFormat:@"The client is unable to contact the resource at %@", [[self URL] absoluteString]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
@@ -157,14 +236,13 @@
NSData* payload = nil;
RKResponse* response = nil;
if ([[RKClient sharedClient] isNetworkAvailable]) {
if ([self shouldDispatchRequest]) {
[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:RKRequestSentNotification object:self userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestSentNotification object:self userInfo:nil];
_isLoading = YES;
payload = [NSURLConnection sendSynchronousRequest:_URLRequest returningResponse:&URLResponse error:&error];
@@ -191,14 +269,7 @@
}
- (void)cancel {
if ([_delegate respondsToSelector:@selector(requestDidCancel:)]) {
[_delegate requestDidCancel:self];
}
_delegate = nil;
[_connection cancel];
[_connection release];
_connection = nil;
_isLoading = NO;
[self cancelAndInformDelegate:YES];
}
- (void)didFailLoadWithError:(NSError*)error {
@@ -208,34 +279,32 @@
[_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:RKRequestFailedWithErrorNotification object:self userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestFailedWithErrorNotification object:self userInfo:nil];
}
- (void)didFinishLoad:(RKResponse*)response {
_isLoading = NO;
_isLoaded = YES;
_isLoading = NO;
_isLoaded = YES;
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
[_delegate request:self didLoadResponse:response];
}
if ([_delegate respondsToSelector:@selector(request:didLoadResponse:)]) {
[_delegate request:self didLoadResponse:response];
}
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:response forKey:@"response"];
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification object:self userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:RKResponseReceivedNotification object:response userInfo:nil];
// TODO: Should be factored out in favor of notification. UIKit specific.
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];
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:response forKey:@"response"];
[[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification object:self userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:RKResponseReceivedNotification object:response userInfo:nil];
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 {
@@ -275,4 +344,15 @@
return [[self resourcePath] isEqualToString:resourcePath];
}
- (void)appDidEnterBackgroundNotification:(NSNotification*)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
if (self.backgroundPolicy == RKRequestBackgroundPolicyCancel) {
[self cancel];
} else if (self.backgroundPolicy == RKRequestBackgroundPolicyRequeue) {
// Cancel the existing request
[self cancelAndInformDelegate:NO];
[self send];
}
}
@end