Permalink
Browse files

Merge branch 'master' of git://github.com/adamjernst/AEURLConnection

  • Loading branch information...
2 parents 13494a0 + 2e7580d commit 80985423d2e779ee7d15d9064f5d62ca112479a3 @adamjernst committed Mar 10, 2012
View
25 AEURLConnection/AEURLConnection.m
@@ -9,10 +9,12 @@
#import "AEURLConnection.h"
-@interface AEURLConnectionRequest : NSObject
+@interface AEURLConnectionRequest : NSObject {
+ id _handler;
+}
- (id)initWithRequest:(NSURLRequest *)request
queue:(NSOperationQueue *)queue
- processor:(AEURLResponseProcessor)processor
+ processor:(AEURLResponseProcessor)processor
completionHandler:handler;
@property (nonatomic, retain, readonly) NSURLRequest *request;
@property (nonatomic, retain, readonly) NSOperationQueue *queue;
@@ -282,7 +284,7 @@ @implementation AEURLConnectionRequest
@synthesize request=_request;
@synthesize queue=_queue;
@synthesize processor=_processor;
-@synthesize handler=_handler;
+@dynamic handler;
@synthesize connection=_connection;
@synthesize response=_response;
@@ -315,5 +317,22 @@ - (void)dealloc {
[super dealloc];
}
+// The |handler| getter and setter are dynamic instead of synthesized to
+// make sure that the runtime doesn't pull any tricks with autorelease
+// (which would negate our guarantee that handler is released on the target
+// queue). Thanks to Mike Ash for suggesting I avoid synthesized getters for
+// this reason.
+
+- (id)handler {
+ return _handler;
+}
+
+- (void)setHandler:(id)newHandler {
+ if (newHandler != _handler) {
+ [_handler release];
+ _handler = [newHandler retain];
+ }
+}
+
@end
View
12 AEURLConnection/AEURLRequestFactory.h
@@ -49,14 +49,16 @@ typedef id (^AEURLParameterProcessor)(NSDictionary *parameters, NSMutableURLRequ
// x-www-form-urlencoded format, like a browser's POST form encoding.
+ (AEURLParameterProcessor)formURLEncodedProcessor;
-// A utility function to url-encode a value.
-NSString * AEURLEncodedStringFromString(NSString *string);
+// Utility functions to url-encode and -decode values.
+NSString *AEURLEncodedStringFromString(NSString *string);
+NSString *AEStringFromURLEncodedString(NSString *formEncodedString);
-// A utility function to turn a dictionary into a urlencoded string.
-NSString * AEQueryStringFromParameters(NSDictionary *parameters);
+// Utility function to turn a dictionary into a urlencoded string and vice-versa.
+NSString *AEQueryStringFromParameters(NSDictionary *parameters);
+NSDictionary *AEParametersFromQueryString(NSString *queryString);
// A utility function to base-64 encode some data.
-NSString * AEBase64EncodedStringFromData(NSData *data);
+NSString *AEBase64EncodedStringFromData(NSData *data);
// See AEJSONProcessor for a parameter processing block that creates JSON.
View
43 AEURLConnection/AEURLRequestFactory.m
@@ -104,13 +104,13 @@ + (AEURLParameterProcessor)formURLEncodedProcessor {
// AF in the function prefix to prevent linker conflicts). Thanks AFNetworking!
// (Used with permission.)
-NSString * AEURLEncodedStringFromString(NSString *string) {
+NSString *AEURLEncodedStringFromString(NSString *string) {
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
}
-NSString * AEQueryStringFromParameters(NSDictionary *parameters) {
+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])];
@@ -120,6 +120,45 @@ + (AEURLParameterProcessor)formURLEncodedProcessor {
return [mutableParameterComponents componentsJoinedByString:@"&"];
}
+// AEStringFromURLEncodedString and AEParametersFromQueryString
+// are loosely based on Google Web Toolkit.
+
+NSString *AEStringFromURLEncodedString(NSString *formEncodedString) {
+ NSMutableString *resultString = [NSMutableString stringWithString:formEncodedString];
+ [resultString replaceOccurrencesOfString:@"+"
+ withString:@" "
+ options:NSLiteralSearch
+ range:NSMakeRange(0, [resultString length])];
+ return [resultString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+}
+
+NSDictionary *AEParametersFromQueryString(NSString *queryString) {
+ NSMutableDictionary *ret = [NSMutableDictionary dictionary];
+ NSArray *components = [queryString componentsSeparatedByString:@"&"];
+ // Use reverse order so that the first occurrence of a key replaces later ones.
+ [components enumerateObjectsWithOptions:NSEnumerationReverse
+ usingBlock:^(id component, NSUInteger idx, BOOL *stop) {
+ if ([component length] == 0) return;
+
+ NSRange pos = [component rangeOfString:@"="];
+ NSString *key;
+ NSString *val;
+ if (pos.location == NSNotFound) {
+ key = AEStringFromURLEncodedString(component);
+ val = @"";
+ } else {
+ key = AEStringFromURLEncodedString([component substringToIndex:pos.location]);
+ val = AEStringFromURLEncodedString([component substringFromIndex:pos.location + pos.length]);
+ }
+ // stringByUnescapingQueryString returns nil on invalid UTF8
+ // and NSMutableDictionary raises an exception when passed nil values.
+ if (!key) key = @"";
+ if (!val) val = @"";
+ [ret setObject:val forKey:key];
+ }];
+ return ret;
+}
+
NSString * AEBase64EncodedStringFromData(NSData *data) {
NSUInteger length = [data length];
NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
View
12 Example/AEURLExample.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ C42113D814A4D012006E19D2 /* AEURLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */; };
+ C42113D914A4D012006E19D2 /* AEURLResponseProcessors.m in Sources */ = {isa = PBXBuildFile; fileRef = C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */; };
C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB051447332A00228625 /* AEJSONProcessor.m */; };
C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB1914473D0A00228625 /* AEURLRequestFactory.m */; };
C4C186871445FF9C003DBCC0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186861445FF9C003DBCC0 /* UIKit.framework */; };
@@ -38,6 +40,10 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestOperation.h; sourceTree = "<group>"; };
+ C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLRequestOperation.m; sourceTree = "<group>"; };
+ C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLResponseProcessors.h; sourceTree = "<group>"; };
+ C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLResponseProcessors.m; sourceTree = "<group>"; };
C448BB041447332A00228625 /* AEJSONProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEJSONProcessor.h; sourceTree = "<group>"; };
C448BB051447332A00228625 /* AEJSONProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEJSONProcessor.m; sourceTree = "<group>"; };
C448BB1814473D0A00228625 /* AEURLRequestFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestFactory.h; sourceTree = "<group>"; };
@@ -181,6 +187,10 @@
C448BB1914473D0A00228625 /* AEURLRequestFactory.m */,
C4E6E3531447CCCB000AEDA7 /* AEExpect.h */,
C4E6E3541447CCCB000AEDA7 /* AEExpect.m */,
+ C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */,
+ C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */,
+ C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */,
+ C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */,
);
name = AEURLConnection;
path = ../AEURLConnection;
@@ -301,6 +311,8 @@
C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */,
C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */,
C4E6E3551447CCCB000AEDA7 /* AEExpect.m in Sources */,
+ C42113D814A4D012006E19D2 /* AEURLRequestOperation.m in Sources */,
+ C42113D914A4D012006E19D2 /* AEURLResponseProcessors.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
3 Example/AEURLExample/AEViewController.m
@@ -10,6 +10,7 @@
#import "AEURLConnection.h"
#import "AEJSONProcessor.h"
#import "AEExpect.h"
+#import "AEURLResponseProcessors.h"
@interface AEViewController ()
@property (nonatomic, retain) NSArray *keys;
@@ -33,7 +34,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]
- processor:[AEURLConnection chainedResponseProcessor:
+ processor:[AEURLResponseProcessors chainedResponseProcessor:
[AEExpect statusCode:[AEExpect defaultAcceptableStatusCodes]],
[AEExpect contentType:[AEJSONProcessor defaultAcceptableJSONContentTypes]],
[AEJSONProcessor JSONResponseProcessor], nil]
View
12 Example/AEURLExampleTests/AEURLExampleTests.m
@@ -8,6 +8,7 @@
#import "AEURLExampleTests.h"
#import "AEURLConnection.h"
+#import "AEURLRequestFactory.h"
// This object simulates UIViewController, which can only be released on the
// main thread. (This is the root of "The Deallocation Problem"; search the
@@ -62,6 +63,17 @@ - (void)testMainThreadReleaseOnlyObject {
}
}
+- (void)testQueryStringEncode {
+ NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:
+ @"", @"empty_value",
+ @"blah", @"boring_value",
+ @"†é߆", @"aççén†é∂", nil];
+ NSString *s = AEQueryStringFromParameters(d);
+ NSDictionary *d2 = AEParametersFromQueryString(s);
+
+ STAssertEqualObjects(d, d2, @"Query string encoding or decoding failed");
+}
+
@end

0 comments on commit 8098542

Please sign in to comment.