Skip to content

Commit

Permalink
Add a new request factory class. Refactory JSON processing blocks acc…
Browse files Browse the repository at this point in the history
…ordingly.
  • Loading branch information
adamjernst committed Oct 13, 2011
1 parent ec5084a commit 0d322f9
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 15 deletions.
11 changes: 9 additions & 2 deletions AEURLConnection/AEJSONProcessingBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
15 changes: 11 additions & 4 deletions AEURLConnection/AEJSONProcessingBlock.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions AEURLConnection/AEURLConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#import <Foundation/Foundation.h>

typedef id (^AEURLConnectionProcessingBlock)(NSURLResponse *, NSData *, NSError **);
typedef id (^AEURLConnectionResponseProcessingBlock)(NSURLResponse *, NSData *, NSError **);

@interface AEURLConnection : NSObject

Expand All @@ -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 changes: 6 additions & 6 deletions AEURLConnection/AEURLConnection.m
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
53 changes: 53 additions & 0 deletions AEURLConnection/AEURLRequestFactory.h
Original file line number Diff line number Diff line change
@@ -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
157 changes: 157 additions & 0 deletions AEURLConnection/AEURLRequestFactory.m
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions Example/AEURLExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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; };
Expand Down Expand Up @@ -170,6 +173,8 @@
C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */,
C448BB041447332A00228625 /* AEJSONProcessingBlock.h */,
C448BB051447332A00228625 /* AEJSONProcessingBlock.m */,
C448BB1814473D0A00228625 /* AEURLRequestFactory.h */,
C448BB1914473D0A00228625 /* AEURLRequestFactory.m */,
);
name = AEURLConnection;
path = ../AEURLConnection;
Expand Down Expand Up @@ -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;
};
Expand Down
2 changes: 1 addition & 1 deletion Example/AEURLExample/AEViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down

0 comments on commit 0d322f9

Please sign in to comment.