Permalink
Browse files

Add a new request factory class. Refactory JSON processing blocks acc…

…ordingly.
  • Loading branch information...
1 parent ec5084a commit 0d322f9073c7b49438fd1b8decf04f505004cd09 @adamjernst committed Oct 13, 2011
@@ -8,6 +8,7 @@
#import <Foundation/Foundation.h>
#import "AEURLConnection.h"
+#import "AEURLRequestFactory.h"
// AEJSONProcessingBlock requires JSONKit. You can use AEURLConnection
// without JSONKit; just remove the AEJSONProcessingBlock.m/h files from your
@@ -17,7 +18,13 @@
@interface AEJSONProcessingBlock : NSObject
-+ (AEURLConnectionProcessingBlock)JSONProcessingBlock;
-+ (AEURLConnectionProcessingBlock)JSONProcessingBlockWithOptions:(JKParseOptionFlags)options;
+// These blocks are used to process a response from the server.
++ (AEURLConnectionResponseProcessingBlock)JSONResponseProcessingBlock;
++ (AEURLConnectionResponseProcessingBlock)JSONResponseProcessingBlockWithOptions:(JKParseOptionFlags)options;
+
+// This block will put parameters into a NSMutableURLRequest's HTTP body,
+// encoded as JSON, and set the request's Content-Type header to
+// "application/json; charset=UTF-8".
++ (AEURLConnectionParameterProcessingBlock)JSONParameterProcessingBlock;
@end
@@ -10,20 +10,27 @@
@implementation AEJSONProcessingBlock
-static AEURLConnectionProcessingBlock JSONProcessingBlock = nil;
+static AEURLConnectionResponseProcessingBlock JSONProcessingBlock = nil;
-+ (AEURLConnectionProcessingBlock)JSONProcessingBlock {
++ (AEURLConnectionResponseProcessingBlock)JSONResponseProcessingBlock {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
- JSONProcessingBlock = [[self JSONProcessingBlockWithOptions:JKParseOptionNone] retain];
+ JSONProcessingBlock = [[self JSONResponseProcessingBlockWithOptions:JKParseOptionNone] retain];
});
return JSONProcessingBlock;
}
-+ (AEURLConnectionProcessingBlock)JSONProcessingBlockWithOptions:(JKParseOptionFlags)options {
++ (AEURLConnectionResponseProcessingBlock)JSONResponseProcessingBlockWithOptions:(JKParseOptionFlags)options {
return [[(id)^(NSURLResponse *response, NSData *data, NSError **error){
return [data objectFromJSONDataWithParseOptions:options error:error];
} copy] autorelease];
}
++ (AEURLConnectionParameterProcessingBlock)JSONParameterProcessingBlock {
+ return [[^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){
+ [targetRequest setHTTPBody:[parameters JSONData]];
+ [targetRequest setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
+ } copy] autorelease];
+}
+
@end
@@ -8,7 +8,7 @@
#import <Foundation/Foundation.h>
-typedef id (^AEURLConnectionProcessingBlock)(NSURLResponse *, NSData *, NSError **);
+typedef id (^AEURLConnectionResponseProcessingBlock)(NSURLResponse *, NSData *, NSError **);
@interface AEURLConnection : NSObject
@@ -27,7 +27,7 @@ typedef id (^AEURLConnectionProcessingBlock)(NSURLResponse *, NSData *, NSError
// Check out AEJSONProcessingBlock for an example usage.
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
- processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ processingBlock:(AEURLConnectionResponseProcessingBlock)processingBlock
completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler;
@end
@@ -12,14 +12,14 @@
@interface AEURLConnectionRequest : NSObject
- (id)initWithRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
- processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ processingBlock:(AEURLConnectionResponseProcessingBlock)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;
+// UIViewController or you'll be vulnerable to the Deallocation Problem.
+@property (nonatomic, copy, readonly) AEURLConnectionResponseProcessingBlock 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.
@@ -52,7 +52,7 @@ + (void)sendAsynchronousRequest:(NSURLRequest *)request
+ (void)sendAsynchronousRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
- processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ processingBlock:(AEURLConnectionResponseProcessingBlock)processingBlock
completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler {
AEURLConnectionRequest *req = [[AEURLConnectionRequest alloc] initWithRequest:request queue:queue processingBlock:processingBlock completionHandler:handler];
[[AEURLConnectionManager sharedManager] startRequest:req];
@@ -241,7 +241,7 @@ - (void)executeHandlerForConnection:(NSURLConnection *)connection error:(NSError
});
dispatch_async(processing_queue, ^{
- AEURLConnectionProcessingBlock processor = [req processingBlock];
+ AEURLConnectionResponseProcessingBlock processor = [req processingBlock];
NSError *error = nil;
id processedData = processor([req response], [req data], &error);
if (processedData) {
@@ -289,7 +289,7 @@ @implementation AEURLConnectionRequest
- (id)initWithRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
- processingBlock:(AEURLConnectionProcessingBlock)processingBlock
+ processingBlock:(AEURLConnectionResponseProcessingBlock)processingBlock
completionHandler:(id)handler {
self = [super init];
if (self) {
@@ -0,0 +1,53 @@
+//
+// AEURLRequestFactory.h
+// AEURLExample
+//
+// Created by Adam Ernst on 10/13/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef id (^AEURLConnectionParameterProcessingBlock)(NSDictionary *parameters, NSMutableURLRequest *targetRequest);
+
+@interface AEURLRequestFactory : NSObject {
+ NSMutableDictionary *_defaultHeaderValues;
+}
+
+// A singleton request factory. If you set any default header values on this
+// factory, they persist to future uses.
++ (AEURLRequestFactory *)defaultFactory;
+
+// This method puts parameters in the query string if method is GET; otherwise,
+// it puts them in the HTTP body as x-www-form-urlencoded (just like a browser-
+// submitted POST form).
+// Need to modify the returned request further? Just use -mutableCopy.
+- (NSURLRequest *)requestWithURL:(NSURL *)url
+ method:(NSString *)method
+ parameters:(NSDictionary *)parameters;
+
+// You can pass any block to put the parameters into the generated request: e.g.
+// JSON, or you could write your own for XML or other encodings.
+- (NSURLRequest *)requestWithURL:(NSURL *)url
+ method:(NSString *)method
+ parameters:(NSDictionary *)parameters
+ parameterProcessingBlock:(AEURLConnectionParameterProcessingBlock)parameterProcessingBlock;
+
+- (NSString *)defaultValueForHeader:(NSString *)header;
+- (void)setDefaultValue:(NSString *)value forHeader:(NSString *)header;
+
+// Use this utility functions with setDefaultValue:forHeader:, passing
+// "Authorization" for the header.
++ (NSString *)authorizationHeaderForUsername:(NSString *)username password:(NSString *)password;
+
+// A parameter processing block that puts the parameters into the query string
+// (usually for GET requests).
++ (AEURLConnectionParameterProcessingBlock)queryStringProcessingBlock;
+
+// A parameter processing block that puts the parameters into the HTTP body in
+// x-www-form-urlencoded format, like a browser's POST form encoding.
++ (AEURLConnectionParameterProcessingBlock)formURLEncodedProcessingBlock;
+
+// See AEJSONProcessingBlock for a parameter processing block that creates JSON.
+
+@end
@@ -0,0 +1,157 @@
+//
+// AEURLRequestFactory.m
+// AEURLExample
+//
+// Created by Adam Ernst on 10/13/11.
+// Copyright (c) 2011 cosmicsoft. All rights reserved.
+//
+
+#import "AEURLRequestFactory.h"
+
+// Forward declarations:
+static NSString * AEURLEncodedStringFromString(NSString *string);
+static NSString * AEQueryStringFromParameters(NSDictionary *parameters);
+static NSString * AEBase64EncodedStringFromString(NSString *string);
+
+@implementation AEURLRequestFactory
+
++ (AEURLRequestFactory *)defaultFactory {
+ static AEURLRequestFactory *defaultFactory;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ defaultFactory = [[AEURLRequestFactory alloc] init];
+ });
+ return defaultFactory;
+}
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ _defaultHeaderValues = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_defaultHeaderValues release];
+ [super dealloc];
+}
+
+- (NSURLRequest *)requestWithURL:(NSURL *)url
+ method:(NSString *)method
+ parameters:(NSDictionary *)parameters {
+ AEURLConnectionParameterProcessingBlock processingBlock = nil;
+ if ([method isEqualToString:@"GET"]) {
+ processingBlock = [AEURLRequestFactory queryStringProcessingBlock];
+ } else {
+ processingBlock = [AEURLRequestFactory formURLEncodedProcessingBlock];
+ }
+ return [self requestWithURL:url method:method parameters:parameters parameterProcessingBlock:processingBlock];
+}
+
+- (NSURLRequest *)requestWithURL:(NSURL *)url
+ method:(NSString *)method
+ parameters:(NSDictionary *)parameters
+ parameterProcessingBlock:(AEURLConnectionParameterProcessingBlock)parameterProcessingBlock {
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
+ [request setHTTPMethod:method];
+ [request setAllHTTPHeaderFields:_defaultHeaderValues];
+ parameterProcessingBlock(parameters, request);
+ return request;
+}
+
+#pragma mark - Default Header Values
+
+- (NSString *)defaultValueForHeader:(NSString *)header {
+ return [_defaultHeaderValues objectForKey:header];
+}
+
+- (void)setDefaultValue:(NSString *)value forHeader:(NSString *)header {
+ [_defaultHeaderValues setObject:value forKey:header];
+}
+
+#pragma mark - Authorization Header Generation
+
++ (NSString *)authorizationHeaderForUsername:(NSString *)username password:(NSString *)password {
+ return [NSString stringWithFormat:@"Basic %@", AEBase64EncodedStringFromString([NSString stringWithFormat:@"%@:%@", username, password])];
+}
+
+#pragma mark - Parameter Encoding Blocks
+
++ (AEURLConnectionParameterProcessingBlock)queryStringProcessingBlock {
+ static AEURLConnectionParameterProcessingBlock queryStringProcessingBlock;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ queryStringProcessingBlock = [^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){
+ NSString *oldURL = [[targetRequest URL] absoluteString];
+ NSURL *newURL = [NSURL URLWithString:[oldURL stringByAppendingFormat:[oldURL rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AEQueryStringFromParameters(parameters)]];
+ [targetRequest setURL:newURL];
+ } copy];
+ });
+ return queryStringProcessingBlock;
+}
+
++ (AEURLConnectionParameterProcessingBlock)formURLEncodedProcessingBlock {
+ static AEURLConnectionParameterProcessingBlock formURLEncodedProcessingBlock;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ formURLEncodedProcessingBlock = [^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){
+ [targetRequest setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
+ [targetRequest setHTTPBody:[AEQueryStringFromParameters(parameters) dataUsingEncoding:NSUTF8StringEncoding]];
+ } copy];
+ });
+ return formURLEncodedProcessingBlock;
+}
+
+#pragma mark - URLEncoding
+
+// These functions are based on AFNetworking's equivalents (substituting AE for
+// AF in the function prefix to prevent linker conflicts). Thanks AFNetworking!
+// (Used with permission.)
+
+static NSString * AEURLEncodedStringFromString(NSString *string) {
+ static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
+
+ return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
+}
+
+static NSString * AEQueryStringFromParameters(NSDictionary *parameters) {
+ NSMutableArray *mutableParameterComponents = [NSMutableArray array];
+ for (id key in [parameters allKeys]) {
+ NSString *component = [NSString stringWithFormat:@"%@=%@", AEURLEncodedStringFromString([key description]), AEURLEncodedStringFromString([[parameters valueForKey:key] description])];
+ [mutableParameterComponents addObject:component];
+ }
+
+ return [mutableParameterComponents componentsJoinedByString:@"&"];
+}
+
+static NSString * AEBase64EncodedStringFromString(NSString *string) {
+ NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string length]];
+ NSUInteger length = [data length];
+ NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
+
+ uint8_t *input = (uint8_t *)[data bytes];
+ uint8_t *output = (uint8_t *)[mutableData mutableBytes];
+
+ for (NSUInteger i = 0; i < length; i += 3) {
+ NSUInteger value = 0;
+ for (NSUInteger j = i; j < (i + 3); j++) {
+ value <<= 8;
+ if (j < length) {
+ value |= (0xFF & input[j]);
+ }
+ }
+
+ static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ NSUInteger idx = (i / 3) * 4;
+ output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F];
+ output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F];
+ output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
+ output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
+ }
+
+ return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease];
+}
+
+@end
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
C448BB061447332A00228625 /* AEJSONProcessingBlock.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB051447332A00228625 /* AEJSONProcessingBlock.m */; };
+ C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB1914473D0A00228625 /* AEURLRequestFactory.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 */; };
@@ -38,6 +39,8 @@
/* 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>"; };
+ C448BB1814473D0A00228625 /* AEURLRequestFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestFactory.h; sourceTree = "<group>"; };
+ C448BB1914473D0A00228625 /* AEURLRequestFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLRequestFactory.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; };
@@ -170,6 +173,8 @@
C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */,
C448BB041447332A00228625 /* AEJSONProcessingBlock.h */,
C448BB051447332A00228625 /* AEJSONProcessingBlock.m */,
+ C448BB1814473D0A00228625 /* AEURLRequestFactory.h */,
+ C448BB1914473D0A00228625 /* AEURLRequestFactory.m */,
);
name = AEURLConnection;
path = ../AEURLConnection;
@@ -288,6 +293,7 @@
C4C186C31445FFD7003DBCC0 /* AEURLConnection.m in Sources */,
C4C186CB144615CF003DBCC0 /* JSONKit.m in Sources */,
C448BB061447332A00228625 /* AEJSONProcessingBlock.m in Sources */,
+ C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -32,7 +32,7 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://graph.facebook.com/137947732957611"]];
[AEURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
- processingBlock:[AEJSONProcessingBlock JSONProcessingBlock]
+ processingBlock:[AEJSONProcessingBlock JSONResponseProcessingBlock]
completionHandler:^(NSURLResponse *response, id data, NSError *error) {
[spinner stopAnimating];

0 comments on commit 0d322f9

Please sign in to comment.