From a53f28e3394c45a67e47695347151b7320a58845 Mon Sep 17 00:00:00 2001 From: Blake Watters Date: Wed, 1 Dec 2010 14:56:16 -0500 Subject: [PATCH] First cut at queue support. --- Code/Network/RKNotifications.h | 1 + Code/Network/RKNotifications.m | 1 + Code/Network/RKRequest.h | 6 +- Code/Network/RKRequest.m | 5 ++ Code/Network/RKRequestQueue.h | 63 +++++++++++++ Code/Network/RKRequestQueue.m | 141 ++++++++++++++++++++++++++++++ Code/Network/RKResponse.m | 18 ++-- RestKit.xcodeproj/project.pbxproj | 10 ++- 8 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 Code/Network/RKRequestQueue.h create mode 100644 Code/Network/RKRequestQueue.m diff --git a/Code/Network/RKNotifications.h b/Code/Network/RKNotifications.h index 7f0a1c43..59866d14 100644 --- a/Code/Network/RKNotifications.h +++ b/Code/Network/RKNotifications.h @@ -18,3 +18,4 @@ */ extern NSString* const kRKRequestSentNotification; extern NSString* const kRKResponseReceivedNotification; +extern NSString* const kRKRequestFailedWithErrorNotification; \ No newline at end of file diff --git a/Code/Network/RKNotifications.m b/Code/Network/RKNotifications.m index dc3dbea6..ff69d220 100644 --- a/Code/Network/RKNotifications.m +++ b/Code/Network/RKNotifications.m @@ -10,3 +10,4 @@ NSString* const kRKRequestSentNotification = @"kRKRequestSentNotification"; NSString* const kRKResponseReceivedNotification = @"kRKRespongReceivedNotification"; +NSString* const kRKRequestFailedWithErrorNotification = @"kRKRequestFailedWithErrorNotification"; diff --git a/Code/Network/RKRequest.h b/Code/Network/RKRequest.h index 333e4a99..8e5ecbea 100644 --- a/Code/Network/RKRequest.h +++ b/Code/Network/RKRequest.h @@ -61,11 +61,13 @@ typedef enum RKRequestMethod { * If the object implements the RKRequestDelegate protocol, * it will receive request lifecycle event messages. */ +// TODO: Should be RKRequestDelegate instead of id @property(nonatomic, assign) id delegate; /** * The selector to invoke when the request is completed */ +// TODO: Eliminate callback in favor of a delegate method (requestDidLoadResponse:) for simplicity @property(nonatomic, assign) SEL callback; /** @@ -113,7 +115,8 @@ typedef enum RKRequestMethod { - (id)initWithURL:(NSURL*)URL delegate:(id)delegate callback:(SEL)callback; /** - * Send the request asynchronously + * Send the request asynchronously. It will be added to the queue and + * dispatched as soon as possible. */ - (void)send; @@ -154,6 +157,7 @@ typedef enum RKRequestMethod { * * Modeled off of TTURLRequest */ +// TODO: Add a didLoadResponse: delegate method in place off callback @protocol RKRequestDelegate @optional diff --git a/Code/Network/RKRequest.m b/Code/Network/RKRequest.m index 0dc3e8f9..751fd65c 100644 --- a/Code/Network/RKRequest.m +++ b/Code/Network/RKRequest.m @@ -7,6 +7,7 @@ // #import "RKRequest.h" +#import "RKRequestQueue.h" #import "RKResponse.h" #import "NSDictionary+RKRequestSerialization.h" #import "RKNotifications.h" @@ -115,6 +116,10 @@ } - (void)send { + [[RKRequestQueue sharedQueue] sendRequest:self]; +} + +- (void)fireAsynchronousRequest { [self addHeadersToRequest]; NSString* body = [[NSString alloc] initWithData:[_URLRequest HTTPBody] encoding:NSUTF8StringEncoding]; NSLog(@"Sending %@ request to URL %@. HTTP Body: %@", [self HTTPMethod], [[self URL] absoluteString], body); diff --git a/Code/Network/RKRequestQueue.h b/Code/Network/RKRequestQueue.h new file mode 100644 index 00000000..474bb5d0 --- /dev/null +++ b/Code/Network/RKRequestQueue.h @@ -0,0 +1,63 @@ +// +// RKRequestQueue.h +// RestKit +// +// Created by Blake Watters on 12/1/10. +// Copyright 2010 Two Toasters. All rights reserved. +// + +#import +#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*)delegate; + +/** + * Cancel all active or pending requests. + */ +- (void)cancelAllRequests; + +@end diff --git a/Code/Network/RKRequestQueue.m b/Code/Network/RKRequestQueue.m new file mode 100644 index 00000000..bff7e854 --- /dev/null +++ b/Code/Network/RKRequestQueue.m @@ -0,0 +1,141 @@ +// +// 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 (int i = 0; + i < kMaxConcurrentLoads && _totalLoading < kMaxConcurrentLoads + && _requests.count; + ++i) { + RKRequest* request = [[_requests objectAtIndex:0] retain]; + [_requests removeObjectAtIndex:0]; + [self dispatchRequest:request]; + [request release]; + } + + 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 { + if (_suspended || _totalLoading == kMaxConcurrentLoads) { + [_requests addObject:request]; + } else { + ++_totalLoading; + [self dispatchRequest:request]; + } +} + +- (void)cancelRequest:(RKRequest*)request { + [request cancel]; +} + +- (void)cancelRequestsWithDelegate:(NSObject*)delegate { + for (RKRequest* request in _requests) { + if (request.delegate && request.delegate == delegate) { + [request cancel]; + } + } +} + +- (void)cancelAllRequests { + for (RKRequest* request in [[[_requests copy] autorelease] objectEnumerator]) { + [request cancel]; + } +} + +/** + * Invoked via observation when a request has loaded a response. Remove + * the completed request from the queue and continue processing + */ +- (void)responseDidLoad:(NSNotification*)notification { + _totalLoading--; + [self loadNextInQueue]; +} + +@end diff --git a/Code/Network/RKResponse.m b/Code/Network/RKResponse.m index b2b6772c..16b3d8c8 100644 --- a/Code/Network/RKResponse.m +++ b/Code/Network/RKResponse.m @@ -53,7 +53,7 @@ } // Handle basic auth --(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge previousFailureCount] == 0) { NSURLCredential *newCredential; newCredential=[NSURLCredential credentialWithUser:[NSString stringWithFormat:@"%@", _request.username] @@ -81,16 +81,16 @@ _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]; - +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { [[_request delegate] performSelector:[_request callback] withObject:self]; if ([[_request delegate] respondsToSelector:@selector(requestDidFinishLoad:)]) { [[_request delegate] requestDidFinishLoad:_request]; } + + NSDate* receivedAt = [NSDate date]; + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[_request HTTPMethod], @"HTTPMethod", [_request URL], @"URL", receivedAt, @"receivedAt", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:kRKResponseReceivedNotification object:self userInfo:userInfo]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { @@ -100,6 +100,11 @@ if ([[_request delegate] respondsToSelector:@selector(request:didFailLoadWithError:)]) { [[_request delegate] request:_request didFailLoadWithError:error]; } + + NSDate* receivedAt = [NSDate date]; + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[_request HTTPMethod], @"HTTPMethod", + [_request URL], @"URL", receivedAt, @"receivedAt", error, @"error", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:kRKRequestFailedWithErrorNotification object:_request userInfo:userInfo]; } - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { @@ -108,7 +113,6 @@ } } - - (NSString*)localizedStatusCodeString { return [NSHTTPURLResponse localizedStringForStatusCode:[self statusCode]]; } diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index 16fd0d26..968efa77 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ 2520776E113587BE00382018 /* NSDictionary+RKRequestSerializationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2520776D113587BE00382018 /* NSDictionary+RKRequestSerializationSpec.m */; }; 2523363E11E7A1F00048F9B4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F6C3A9510FE7524008F47C5 /* UIKit.framework */; }; 2524CB5D1278930200D1314C /* RKParamsAttachmentSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 2524CB5C1278930200D1314C /* RKParamsAttachmentSpec.m */; }; + 2538C05C12A6C44A0006903C /* RKRequestQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 2538C05A12A6C44A0006903C /* RKRequestQueue.h */; }; + 2538C05D12A6C44A0006903C /* RKRequestQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538C05B12A6C44A0006903C /* RKRequestQueue.m */; }; 253A08AF12551EA500976E89 /* Network.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A08AE12551EA500976E89 /* Network.h */; settings = {ATTRIBUTES = (Public, ); }; }; 253A08CC125522CE00976E89 /* NSDictionary+RKRequestSerialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 253A086612551D8D00976E89 /* NSDictionary+RKRequestSerialization.h */; settings = {ATTRIBUTES = (Public, ); }; }; 253A08CD125522D000976E89 /* NSDictionary+RKRequestSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 253A086712551D8D00976E89 /* NSDictionary+RKRequestSerialization.m */; }; @@ -406,6 +408,8 @@ 2520776D113587BE00382018 /* NSDictionary+RKRequestSerializationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+RKRequestSerializationSpec.m"; sourceTree = ""; }; 2523360511E79F090048F9B4 /* libRestKitThree20.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitThree20.a; sourceTree = BUILT_PRODUCTS_DIR; }; 2524CB5C1278930200D1314C /* RKParamsAttachmentSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKParamsAttachmentSpec.m; sourceTree = ""; }; + 2538C05A12A6C44A0006903C /* RKRequestQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKRequestQueue.h; sourceTree = ""; }; + 2538C05B12A6C44A0006903C /* RKRequestQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKRequestQueue.m; sourceTree = ""; }; 253A07FC1255161B00976E89 /* libRestKitNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; }; 253A08031255162C00976E89 /* libRestKitObjectMapping.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitObjectMapping.a; sourceTree = BUILT_PRODUCTS_DIR; }; 253A080C12551D3000976E89 /* libRestKitSupport.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitSupport.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -835,6 +839,8 @@ 253A087B12551D8D00976E89 /* RKResponse.m */, 73FE56C4126CB91600E0F30B /* RKURL.h */, 73FE56C5126CB91600E0F30B /* RKURL.m */, + 2538C05A12A6C44A0006903C /* RKRequestQueue.h */, + 2538C05B12A6C44A0006903C /* RKRequestQueue.m */, ); path = Network; sourceTree = ""; @@ -1199,6 +1205,7 @@ 253A08DF125522E300976E89 /* RKRequestSerializable.h in Headers */, 253A08E0125522E300976E89 /* RKResponse.h in Headers */, 73FE56C7126CB91600E0F30B /* RKURL.h in Headers */, + 2538C05C12A6C44A0006903C /* RKRequestQueue.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1664,6 +1671,7 @@ 253A08DE125522E200976E89 /* RKRequest.m in Sources */, 253A08E1125522E400976E89 /* RKResponse.m in Sources */, 73FE56C8126CB91600E0F30B /* RKURL.m in Sources */, + 2538C05D12A6C44A0006903C /* RKRequestQueue.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1824,7 +1832,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; OTHER_LDFLAGS = "-ObjC"; PREBINDING = NO; - SDKROOT = iphoneos4.1; + SDKROOT = iphoneos; USER_HEADER_SEARCH_PATHS = ../ElementParser/Classes; }; name = Debug;