Permalink
Browse files

New AEURLRequestOperation, for asynchronous network requests using NS…

…Operation (if you need queueing, maxOperations, etc. Also, a new response processor for images, which was moved (along with chainedResponseProcessor) into the new namespace AEURLResponseProcessors.
  • Loading branch information...
1 parent 25d7a02 commit e950112318119ebe8e844eaa8c4044d26bd12a86 @adamjernst committed Nov 10, 2011
@@ -7,12 +7,7 @@
//
#import <Foundation/Foundation.h>
-
-// This block can be used as a parameter to |processor:|.
-// It is REQUIRED that the block either returns an object and leaves the error
-// parameter untouched, or sets an error and returns nil. This is enforced with
-// an assertion.
-typedef id (^AEURLResponseProcessor)(NSURLResponse *, id, NSError **);
+#import "AEURLResponseProcessors.h"
@interface AEURLConnection : NSObject
@@ -34,22 +29,4 @@ typedef id (^AEURLResponseProcessor)(NSURLResponse *, id, NSError **);
processor:(AEURLResponseProcessor)processor
completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler;
-// The real power comes when you chain processors together. This allows
-// you to verify that the status code is acceptable, then that the content-type
-// is what you expect, then parse JSON, and finally require that the response
-// is a dictionary (not an array)--and to do it all in a declarative way.
-// This means you can store the chained processor in your app and reuse it in
-// different contexts, instead of duplicating the logic everywhere.
-
-// Note that because completionHandler returns *either* an NSError *or* data,
-// never both, you cannot get the document data if a response processor fails.
-// If you need to access the HTTP body, you'll need to do your response
-// processing the old fashioned way.
-
-// The returned processor will run each processor in sequence. If one fails by
-// returning an NSError*, processing stops immediately and the subsequent
-// processors are not run.
-// Just like NSArray, *the last argument must be nil.*
-+ (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... NS_REQUIRES_NIL_TERMINATION;
-
@end
@@ -57,27 +57,6 @@ + (void)sendAsynchronousRequest:(NSURLRequest *)request
[req release];
}
-+ (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... {
- NSMutableArray *processors = [NSMutableArray array];
- va_list args;
- va_start(args, firstProcessor);
- for (AEURLResponseProcessor processor = firstProcessor; processor != nil; processor = va_arg(args, AEURLResponseProcessor)) {
- [processors addObject:[[processor copy] autorelease]];
- }
-
- return [[^(NSURLResponse *response, id data, NSError **error) {
- id newData = data;
- for (AEURLResponseProcessor processor in processors) {
- newData = processor(response, newData, error);
- if (*error) {
- NSAssert(newData == nil, @"Expected data or error but not both");
- return nil;
- }
- }
- return newData;
- } copy] autorelease];
-}
-
@end
@@ -0,0 +1,27 @@
+//
+// AEURLRequestOperation.h
+// Turntable
+//
+// Created by Adam Ernst on 11/10/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "AEURLConnection.h"
+
+@interface AEURLRequestOperation : NSOperation
+
++ (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request;
++ (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor;
+
+- (id)initWithRequest:(NSURLRequest *)request;
+- (id)initWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor;
+
+@property (nonatomic, retain, readonly) NSURLRequest *request;
+@property (nonatomic, retain, readonly) AEURLResponseProcessor processor;
+
+@property (nonatomic, retain, readonly) NSURLResponse *response;
+@property (nonatomic, retain, readonly) id data;
+@property (nonatomic, retain, readonly) NSError *error;
+
+@end
@@ -0,0 +1,124 @@
+//
+// AEURLRequestOperation.m
+// Turntable
+//
+// Created by Adam Ernst on 11/10/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import "AEURLRequestOperation.h"
+
+typedef enum {
+ AEURLRequestOperationStateInitial,
+ AEURLRequestOperationStateExecuting,
+ AEURLRequestOperationStateFinished,
+} AEURLRequestOperationState;
+
+@interface AEURLRequestOperation ()
+@property (nonatomic, retain, readwrite) NSURLResponse *response;
+@property (nonatomic, retain, readwrite) id data;
+@property (nonatomic, retain, readwrite) NSError *error;
+
+@property (nonatomic) AEURLRequestOperationState state;
+@end
+
+@implementation AEURLRequestOperation
+
+@synthesize request=_request;
+@synthesize processor=_processor;
+@synthesize response=_response;
+@synthesize data=_data;
+@synthesize error=_error;
+@synthesize state=_state;
+
++ (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request {
+ return [[[AEURLRequestOperation alloc] initWithRequest:request] autorelease];
+}
+
++ (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor {
+ return [[[AEURLRequestOperation alloc] initWithRequest:request processor:processor] autorelease];
+}
+
+- (id)initWithRequest:(NSURLRequest *)request {
+ return [self initWithRequest:request processor:nil];
+}
+
+- (id)initWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor {
+ self = [super init];
+ if (self) {
+ _request = [request retain];
+ _processor = [processor copy];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_request release];
+ [_processor release];
+ [_response release];
+ [_data release];
+ [_error release];
+ [super dealloc];
+}
+
+#pragma mark - State
+
+- (void)setState:(AEURLRequestOperationState)newState {
+ AEURLRequestOperationState oldState = _state;
+
+ if ( (newState == AEURLRequestOperationStateExecuting) || (oldState == AEURLRequestOperationStateExecuting) ) {
+ [self willChangeValueForKey:@"isExecuting"];
+ }
+ if (newState == AEURLRequestOperationStateFinished) {
+ [self willChangeValueForKey:@"isFinished"];
+ }
+ _state = newState;
+ if (newState == AEURLRequestOperationStateFinished) {
+ [self didChangeValueForKey:@"isFinished"];
+ }
+ if ( (newState == AEURLRequestOperationStateExecuting) || (oldState == AEURLRequestOperationStateExecuting) ) {
+ [self didChangeValueForKey:@"isExecuting"];
+ }
+}
+
+- (BOOL)isFinished {
+ return [self state] == AEURLRequestOperationStateFinished;
+}
+
+- (BOOL)isExecuting {
+ return [self state] == AEURLRequestOperationStateExecuting;
+}
+
+#pragma mark - NSOperation
+
+- (BOOL)isConcurrent {
+ return YES;
+}
+
+- (void)start {
+ if ([self isCancelled]) {
+ [self setState:AEURLRequestOperationStateFinished];
+ return;
+ }
+
+ static NSOperationQueue *responseQueue;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ responseQueue = [[NSOperationQueue alloc] init];
+ });
+
+ [self setState:AEURLRequestOperationStateExecuting];
+
+ [AEURLConnection sendAsynchronousRequest:[self request]
+ queue:responseQueue
+ processor:[self processor]
+ completionHandler:^(NSURLResponse *response, id data, NSError *error) {
+ [self setResponse:response];
+ [self setData:data];
+ [self setError:error];
+
+ [self setState:AEURLRequestOperationStateFinished];
+ }];
+}
+
+@end
@@ -0,0 +1,46 @@
+//
+// AEURLResponseProcessors.h
+// Turntable
+//
+// Created by Adam Ernst on 11/10/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+// This block can be used as a parameter to |processor:| in AEURLConnection.
+// It is REQUIRED that the block either returns an object and leaves the error
+// parameter untouched, or sets an error and returns nil. This is enforced with
+// an assertion.
+typedef id (^AEURLResponseProcessor)(NSURLResponse *, id, NSError **);
+
+extern NSString *AEURLResponseProcessorsErrorDomain;
+
+enum {
+ AEURLResponseProcessorsErrorImageDecodingFailed = -100,
+};
+
+@interface AEURLResponseProcessors : NSObject
+
+// A basic response processor that decodes an image, or returns an error.
++ (AEURLResponseProcessor)imageResponseProcessor;
+
+// The real power comes when you chain processors together. This allows
+// you to verify that the status code is acceptable, then that the content-type
+// is what you expect, then parse JSON, and finally require that the response
+// is a dictionary (not an array)--and to do it all in a declarative way.
+// This means you can store the chained processor in your app and reuse it in
+// different contexts, instead of duplicating the logic everywhere.
+
+// Note that because completionHandler returns *either* an NSError *or* data,
+// never both, you cannot get the document data if a response processor fails.
+// If you need to access the HTTP body, you'll need to do your response
+// processing the old fashioned way.
+
+// The returned processor will run each processor in sequence. If one fails by
+// returning an NSError*, processing stops immediately and the subsequent
+// processors are not run.
+// Just like NSArray, *the last argument must be nil.*
++ (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... NS_REQUIRES_NIL_TERMINATION;
+
+@end
@@ -0,0 +1,51 @@
+//
+// AEURLResponseProcessors.m
+// Turntable
+//
+// Created by Adam Ernst on 11/10/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import "AEURLResponseProcessors.h"
+
+NSString *AEURLResponseProcessorsErrorDomain = @"AEURLResponseProcessorsErrorDomain";
+
+@implementation AEURLResponseProcessors
+
++ (AEURLResponseProcessor)imageResponseProcessor {
+ return [[^(NSURLResponse *response, id data, NSError **error) {
+ UIImage *image = [[[UIImage alloc] initWithData:data] autorelease];
+ if (!image) {
+ if (error) {
+ *error = [NSError errorWithDomain:AEURLResponseProcessorsErrorDomain
+ code:AEURLResponseProcessorsErrorImageDecodingFailed
+ userInfo:nil];
+ }
+ return nil;
+ }
+ return image;
+ } copy] autorelease];
+}
+
++ (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... {
+ NSMutableArray *processors = [NSMutableArray array];
+ va_list args;
+ va_start(args, firstProcessor);
+ for (AEURLResponseProcessor processor = firstProcessor; processor != nil; processor = va_arg(args, AEURLResponseProcessor)) {
+ [processors addObject:[[processor copy] autorelease]];
+ }
+
+ return [[^(NSURLResponse *response, id data, NSError **error) {
+ id newData = data;
+ for (AEURLResponseProcessor processor in processors) {
+ newData = processor(response, newData, error);
+ if (*error) {
+ NSAssert(newData == nil, @"Expected data or error but not both");
+ return nil;
+ }
+ }
+ return newData;
+ } copy] autorelease];
+}
+
+@end

0 comments on commit e950112

Please sign in to comment.