Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Introduce a decoupled way to process responses without thrashing the …

…CPU.
  • Loading branch information...
commit 7e733e6383c1fe7a2a928cc7ed2ef31cae170ec4 1 parent d03cacf
@adamjernst authored
View
23 AEURLConnection/AEJSONProcessingBlock.h
@@ -0,0 +1,23 @@
+//
+// AEJSONProcessingBlock.h
+// AEURLExample
+//
+// Created by Adam Ernst on 10/13/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "AEURLConnection.h"
+
+// AEJSONProcessingBlock requires JSONKit. You can use AEURLConnection
+// without JSONKit; just remove the AEJSONProcessingBlock.m/h files from your
+// project, and parse JSON manually.
+#import "JSONKit.h"
+
+
+@interface AEJSONProcessingBlock : NSObject
+
++ (AEURLConnectionProcessingBlock)JSONProcessingBlock;
++ (AEURLConnectionProcessingBlock)JSONProcessingBlockWithOptions:(JKParseOptionFlags)options;
+
+@end
View
29 AEURLConnection/AEJSONProcessingBlock.m
@@ -0,0 +1,29 @@
+//
+// AEJSONProcessingBlock.m
+// AEURLExample
+//
+// Created by Adam Ernst on 10/13/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import "AEJSONProcessingBlock.h"
+
+@implementation AEJSONProcessingBlock
+
+static AEURLConnectionProcessingBlock JSONProcessingBlock = nil;
+
++ (AEURLConnectionProcessingBlock)JSONProcessingBlock {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ JSONProcessingBlock = [[self JSONProcessingBlockWithOptions:JKParseOptionNone] retain];
+ });
+ return JSONProcessingBlock;
+}
+
++ (AEURLConnectionProcessingBlock)JSONProcessingBlockWithOptions:(JKParseOptionFlags)options {
+ return [[(id)^(NSURLResponse *response, NSData *data, NSError **error){
+ return [data objectFromJSONDataWithParseOptions:options error:error];
+ } copy] autorelease];
+}
+
+@end
View
16 AEURLConnection/AEURLConnection.h
@@ -8,10 +8,26 @@
#import <Foundation/Foundation.h>
+typedef id (^AEURLConnectionProcessingBlock)(NSURLResponse *, NSData *, NSError **);
+
@interface AEURLConnection : NSObject
+// Mirrors the sendAsynchronousRequest:queue:completionHandler: API from iOS 5,
+// but is safe for use with iOS 4. |completionHandler| is guaranteed to be
+// called *and* released from the context of |queue|.
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue*)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler;
+// You may want to process the response on a background thread, but still safely
+// receive the response on |queue|. This method runs |processingBlock| on a
+// low-priority serial queue (one operation at a time, to prevent thrashing the
+// CPU). completionHandler returns the result of the processing block, instead
+// of an NSData* object.
+// Check out AEJSONProcessingBlock for an example usage.
++ (void)sendAsynchronousRequest:(NSURLRequest *)request
+ queue:(NSOperationQueue *)queue
+ processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler;
+
@end
View
73 AEURLConnection/AEURLConnection.m
@@ -12,13 +12,18 @@
@interface AEURLConnectionRequest : NSObject
- (id)initWithRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
+ processingBlock:(AEURLConnectionProcessingBlock)processingBlock
completionHandler:handler;
@property (nonatomic, retain, readonly) NSURLRequest *request;
@property (nonatomic, retain, readonly) NSOperationQueue *queue;
+// processingBlock released in the background, so don't capture a
+// UIViewController there.
+@property (nonatomic, copy, readonly) AEURLConnectionProcessingBlock processingBlock;
+
// handler is readwrite so that we can nil it out after calling it,
// to ensure it is released on |queue| and not on the network thread.
-@property (nonatomic, copy, readwrite) void (^handler)(NSURLResponse*, NSData*, NSError*);
+@property (nonatomic, copy, readwrite) id handler;
@property (nonatomic, retain, readwrite) NSURLConnection *connection;
@property (nonatomic, retain, readwrite) NSURLResponse *response;
@@ -40,7 +45,16 @@ @implementation AEURLConnection
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue*)queue
completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler {
- AEURLConnectionRequest *req = [[AEURLConnectionRequest alloc] initWithRequest:request queue:queue completionHandler:handler];
+ AEURLConnectionRequest *req = [[AEURLConnectionRequest alloc] initWithRequest:request queue:queue processingBlock:nil completionHandler:handler];
+ [[AEURLConnectionManager sharedManager] startRequest:req];
+ [req release];
+}
+
++ (void)sendAsynchronousRequest:(NSURLRequest *)request
+ queue:(NSOperationQueue *)queue
+ processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler {
+ AEURLConnectionRequest *req = [[AEURLConnectionRequest alloc] initWithRequest:request queue:queue processingBlock:processingBlock completionHandler:handler];
[[AEURLConnectionManager sharedManager] startRequest:req];
[req release];
}
@@ -170,14 +184,16 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[[req data] appendData:data];
}
-- (void)executeHandlerForConnection:(NSURLConnection *)connection error:(NSError *)error {
- AEURLConnectionRequest *req = [self executingRequestForConnection:connection];
+- (void)safelyCallCompletionHandler:(AEURLConnectionRequest *)req error:(NSError *)error data:(id)data {
+ if (error) {
+ NSAssert(data == nil, @"Didn't expect both error and data");
+ }
// It is very important that |handler| is deallocated in the context of
// |queue|, since doing so has the very nice property of solving the thorny
// Deallocation Problem:
// http://developer.apple.com/library/ios/#technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11
- // Note that:
+ // Two approaches to ensuring this that *don't* work:
// - You might create a block that captures |handler|, separately from
// adding it to the queue, and call setHandler:nil between creating that
// block and adding it to the queue, in an attempt to ensure |handler|
@@ -197,25 +213,53 @@ - (void)executeHandlerForConnection:(NSURLConnection *)connection error:(NSError
// So, create a __block variable with a copy of the handler. This prevents
// any kind of variable capture for |handler| itself.
- __block void (^handler)(NSURLResponse *, NSData *, NSError *) = [[req handler] copy];
+ __block void (^handler)(NSURLResponse *, id, NSError *) = [[req handler] copy];
// Now call setHandler:nil on |req|. This should release our last retaining
// reference to |handler| EXCEPT for the __block variable.
[req setHandler:nil];
- // Now |handler| is at +1 retain count. Have it release on |queue| after
- // executing. That guarantees our last reference is released on |queue|.
+ // Now |handler| is at +1 retain count. We release it on |queue| after
+ // executing it. That guarantees our last reference is released on |queue|.
// Note that the block below captures |req|. That is OK since |req| no
// longer has a reference to |handler| (since we called setHandler:nil).
[[req queue] addOperationWithBlock:^{
- handler([req response], error ? nil : [req data], error);
+ handler([req response], data, error);
[handler release];
}];
+}
+
+- (void)executeHandlerForConnection:(NSURLConnection *)connection error:(NSError *)error {
+ AEURLConnectionRequest *req = [self executingRequestForConnection:connection];
+
+ if ([req processingBlock]) {
+ // Create a serial queue to avoid thrashing the CPU.
+ static dispatch_queue_t processing_queue;
+ static dispatch_once_t once_token;
+ dispatch_once(&once_token, ^{
+ processing_queue = dispatch_queue_create("com.adamernst.AEURLConnection.processing", 0);
+ });
+
+ dispatch_async(processing_queue, ^{
+ AEURLConnectionProcessingBlock processor = [req processingBlock];
+ NSError *error = nil;
+ id processedData = processor([req response], [req data], &error);
+ if (processedData) {
+ [self safelyCallCompletionHandler:req error:nil data:processedData];
+ } else {
+ [self safelyCallCompletionHandler:req error:error data:processedData];
+ }
+ });
+ } else {
+ [self safelyCallCompletionHandler:req error:error data:[req data]];
+ }
// Don't remove |req| from |_executingRequests| until this point. Since
// the array is the last retaining reference to |req|, removing it sooner
// will deallocate |req| (and cause us to crash when we try to access its
// properties).
+ // By this point, we're either done accessing req or it's been captured by
+ // a block executing asynchronously.
[_executingRequests removeObject:req];
}
@@ -236,17 +280,22 @@ @implementation AEURLConnectionRequest
@synthesize request=_request;
@synthesize queue=_queue;
+@synthesize processingBlock=_processingBlock;
@synthesize handler=_handler;
@synthesize connection=_connection;
@synthesize response=_response;
@synthesize data=_data;
-- (id)initWithRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(id)handler {
+- (id)initWithRequest:(NSURLRequest *)request
+ queue:(NSOperationQueue *)queue
+ processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ completionHandler:(id)handler {
self = [super init];
if (self) {
_request = [request retain];
_queue = [queue retain];
+ _processingBlock = [processingBlock copy];
_handler = [handler copy];
}
return self;
@@ -255,9 +304,13 @@ - (id)initWithRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue co
- (void)dealloc {
[_request release];
[_queue release];
+ [_processingBlock release];
[_handler release];
+
+ [_connection release];
[_response release];
[_data release];
+
[super dealloc];
}
View
6 Example/AEURLExample.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ C448BB061447332A00228625 /* AEJSONProcessingBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB051447332A00228625 /* AEJSONProcessingBlock.m */; };
C4C186871445FF9C003DBCC0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186861445FF9C003DBCC0 /* UIKit.framework */; };
C4C186891445FF9C003DBCC0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186881445FF9C003DBCC0 /* Foundation.framework */; };
C4C1868B1445FF9C003DBCC0 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C1868A1445FF9C003DBCC0 /* CoreGraphics.framework */; };
@@ -35,6 +36,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ C448BB041447332A00228625 /* AEJSONProcessingBlock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEJSONProcessingBlock.h; sourceTree = "<group>"; };
+ C448BB051447332A00228625 /* AEJSONProcessingBlock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEJSONProcessingBlock.m; sourceTree = "<group>"; };
C4C186821445FF9C003DBCC0 /* AEURLExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AEURLExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
C4C186861445FF9C003DBCC0 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
C4C186881445FF9C003DBCC0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -165,6 +168,8 @@
children = (
C4C186C11445FFD7003DBCC0 /* AEURLConnection.h */,
C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */,
+ C448BB041447332A00228625 /* AEJSONProcessingBlock.h */,
+ C448BB051447332A00228625 /* AEJSONProcessingBlock.m */,
);
name = AEURLConnection;
path = ../AEURLConnection;
@@ -282,6 +287,7 @@
C4C1869A1445FF9C003DBCC0 /* AEViewController.m in Sources */,
C4C186C31445FFD7003DBCC0 /* AEURLConnection.m in Sources */,
C4C186CB144615CF003DBCC0 /* JSONKit.m in Sources */,
+ C448BB061447332A00228625 /* AEJSONProcessingBlock.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
10 Example/AEURLExample/AEViewController.m
@@ -8,7 +8,7 @@
#import "AEViewController.h"
#import "AEURLConnection.h"
-#import "JSONKit.h"
+#import "AEJSONProcessingBlock.h"
@interface AEViewController ()
@property (nonatomic, retain) NSArray *keys;
@@ -32,7 +32,8 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://graph.facebook.com/137947732957611"]];
[AEURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
- completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
+ processingBlock:[AEJSONProcessingBlock JSONProcessingBlock]
+ completionHandler:^(NSURLResponse *response, id data, NSError *error) {
[spinner stopAnimating];
if (error) {
@@ -42,9 +43,8 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
cancelButtonTitle:@"OK"
otherButtonTitles:nil] autorelease] show];
} else {
- id parsedResult = [data objectFromJSONData];
- [self setKeys:[parsedResult allKeys]];
- [self setResult:parsedResult];
+ [self setKeys:[data allKeys]];
+ [self setResult:data];
[[self tableView] reloadData];
}
}];
Please sign in to comment.
Something went wrong with that request. Please try again.