Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

  • Loading branch information...
commit 80985423d2e779ee7d15d9064f5d62ca112479a3 2 parents 13494a0 + 2e7580d
Adam Ernst authored
25 AEURLConnection/AEURLConnection.m
@@ -9,10 +9,12 @@
9 9 #import "AEURLConnection.h"
10 10
11 11
12   -@interface AEURLConnectionRequest : NSObject
  12 +@interface AEURLConnectionRequest : NSObject {
  13 + id _handler;
  14 +}
13 15 - (id)initWithRequest:(NSURLRequest *)request
14 16 queue:(NSOperationQueue *)queue
15   - processor:(AEURLResponseProcessor)processor
  17 + processor:(AEURLResponseProcessor)processor
16 18 completionHandler:handler;
17 19 @property (nonatomic, retain, readonly) NSURLRequest *request;
18 20 @property (nonatomic, retain, readonly) NSOperationQueue *queue;
@@ -282,7 +284,7 @@ @implementation AEURLConnectionRequest
282 284 @synthesize request=_request;
283 285 @synthesize queue=_queue;
284 286 @synthesize processor=_processor;
285   -@synthesize handler=_handler;
  287 +@dynamic handler;
286 288
287 289 @synthesize connection=_connection;
288 290 @synthesize response=_response;
@@ -315,5 +317,22 @@ - (void)dealloc {
315 317 [super dealloc];
316 318 }
317 319
  320 +// The |handler| getter and setter are dynamic instead of synthesized to
  321 +// make sure that the runtime doesn't pull any tricks with autorelease
  322 +// (which would negate our guarantee that handler is released on the target
  323 +// queue). Thanks to Mike Ash for suggesting I avoid synthesized getters for
  324 +// this reason.
  325 +
  326 +- (id)handler {
  327 + return _handler;
  328 +}
  329 +
  330 +- (void)setHandler:(id)newHandler {
  331 + if (newHandler != _handler) {
  332 + [_handler release];
  333 + _handler = [newHandler retain];
  334 + }
  335 +}
  336 +
318 337 @end
319 338
12 AEURLConnection/AEURLRequestFactory.h
@@ -49,14 +49,16 @@ typedef id (^AEURLParameterProcessor)(NSDictionary *parameters, NSMutableURLRequ
49 49 // x-www-form-urlencoded format, like a browser's POST form encoding.
50 50 + (AEURLParameterProcessor)formURLEncodedProcessor;
51 51
52   -// A utility function to url-encode a value.
53   -NSString * AEURLEncodedStringFromString(NSString *string);
  52 +// Utility functions to url-encode and -decode values.
  53 +NSString *AEURLEncodedStringFromString(NSString *string);
  54 +NSString *AEStringFromURLEncodedString(NSString *formEncodedString);
54 55
55   -// A utility function to turn a dictionary into a urlencoded string.
56   -NSString * AEQueryStringFromParameters(NSDictionary *parameters);
  56 +// Utility function to turn a dictionary into a urlencoded string and vice-versa.
  57 +NSString *AEQueryStringFromParameters(NSDictionary *parameters);
  58 +NSDictionary *AEParametersFromQueryString(NSString *queryString);
57 59
58 60 // A utility function to base-64 encode some data.
59   -NSString * AEBase64EncodedStringFromData(NSData *data);
  61 +NSString *AEBase64EncodedStringFromData(NSData *data);
60 62
61 63 // See AEJSONProcessor for a parameter processing block that creates JSON.
62 64
43 AEURLConnection/AEURLRequestFactory.m
@@ -104,13 +104,13 @@ + (AEURLParameterProcessor)formURLEncodedProcessor {
104 104 // AF in the function prefix to prevent linker conflicts). Thanks AFNetworking!
105 105 // (Used with permission.)
106 106
107   -NSString * AEURLEncodedStringFromString(NSString *string) {
  107 +NSString *AEURLEncodedStringFromString(NSString *string) {
108 108 static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
109 109
110 110 return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
111 111 }
112 112
113   -NSString * AEQueryStringFromParameters(NSDictionary *parameters) {
  113 +NSString *AEQueryStringFromParameters(NSDictionary *parameters) {
114 114 NSMutableArray *mutableParameterComponents = [NSMutableArray array];
115 115 for (id key in [parameters allKeys]) {
116 116 NSString *component = [NSString stringWithFormat:@"%@=%@", AEURLEncodedStringFromString([key description]), AEURLEncodedStringFromString([[parameters valueForKey:key] description])];
@@ -120,6 +120,45 @@ + (AEURLParameterProcessor)formURLEncodedProcessor {
120 120 return [mutableParameterComponents componentsJoinedByString:@"&"];
121 121 }
122 122
  123 +// AEStringFromURLEncodedString and AEParametersFromQueryString
  124 +// are loosely based on Google Web Toolkit.
  125 +
  126 +NSString *AEStringFromURLEncodedString(NSString *formEncodedString) {
  127 + NSMutableString *resultString = [NSMutableString stringWithString:formEncodedString];
  128 + [resultString replaceOccurrencesOfString:@"+"
  129 + withString:@" "
  130 + options:NSLiteralSearch
  131 + range:NSMakeRange(0, [resultString length])];
  132 + return [resultString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  133 +}
  134 +
  135 +NSDictionary *AEParametersFromQueryString(NSString *queryString) {
  136 + NSMutableDictionary *ret = [NSMutableDictionary dictionary];
  137 + NSArray *components = [queryString componentsSeparatedByString:@"&"];
  138 + // Use reverse order so that the first occurrence of a key replaces later ones.
  139 + [components enumerateObjectsWithOptions:NSEnumerationReverse
  140 + usingBlock:^(id component, NSUInteger idx, BOOL *stop) {
  141 + if ([component length] == 0) return;
  142 +
  143 + NSRange pos = [component rangeOfString:@"="];
  144 + NSString *key;
  145 + NSString *val;
  146 + if (pos.location == NSNotFound) {
  147 + key = AEStringFromURLEncodedString(component);
  148 + val = @"";
  149 + } else {
  150 + key = AEStringFromURLEncodedString([component substringToIndex:pos.location]);
  151 + val = AEStringFromURLEncodedString([component substringFromIndex:pos.location + pos.length]);
  152 + }
  153 + // stringByUnescapingQueryString returns nil on invalid UTF8
  154 + // and NSMutableDictionary raises an exception when passed nil values.
  155 + if (!key) key = @"";
  156 + if (!val) val = @"";
  157 + [ret setObject:val forKey:key];
  158 + }];
  159 + return ret;
  160 +}
  161 +
123 162 NSString * AEBase64EncodedStringFromData(NSData *data) {
124 163 NSUInteger length = [data length];
125 164 NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
12 Example/AEURLExample.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
7 7 objects = {
8 8
9 9 /* Begin PBXBuildFile section */
  10 + C42113D814A4D012006E19D2 /* AEURLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */; };
  11 + C42113D914A4D012006E19D2 /* AEURLResponseProcessors.m in Sources */ = {isa = PBXBuildFile; fileRef = C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */; };
10 12 C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB051447332A00228625 /* AEJSONProcessor.m */; };
11 13 C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB1914473D0A00228625 /* AEURLRequestFactory.m */; };
12 14 C4C186871445FF9C003DBCC0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186861445FF9C003DBCC0 /* UIKit.framework */; };
@@ -38,6 +40,10 @@
38 40 /* End PBXContainerItemProxy section */
39 41
40 42 /* Begin PBXFileReference section */
  43 + C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestOperation.h; sourceTree = "<group>"; };
  44 + C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLRequestOperation.m; sourceTree = "<group>"; };
  45 + C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLResponseProcessors.h; sourceTree = "<group>"; };
  46 + C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLResponseProcessors.m; sourceTree = "<group>"; };
41 47 C448BB041447332A00228625 /* AEJSONProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEJSONProcessor.h; sourceTree = "<group>"; };
42 48 C448BB051447332A00228625 /* AEJSONProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEJSONProcessor.m; sourceTree = "<group>"; };
43 49 C448BB1814473D0A00228625 /* AEURLRequestFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestFactory.h; sourceTree = "<group>"; };
@@ -181,6 +187,10 @@
181 187 C448BB1914473D0A00228625 /* AEURLRequestFactory.m */,
182 188 C4E6E3531447CCCB000AEDA7 /* AEExpect.h */,
183 189 C4E6E3541447CCCB000AEDA7 /* AEExpect.m */,
  190 + C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */,
  191 + C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */,
  192 + C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */,
  193 + C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */,
184 194 );
185 195 name = AEURLConnection;
186 196 path = ../AEURLConnection;
@@ -301,6 +311,8 @@
301 311 C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */,
302 312 C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */,
303 313 C4E6E3551447CCCB000AEDA7 /* AEExpect.m in Sources */,
  314 + C42113D814A4D012006E19D2 /* AEURLRequestOperation.m in Sources */,
  315 + C42113D914A4D012006E19D2 /* AEURLResponseProcessors.m in Sources */,
304 316 );
305 317 runOnlyForDeploymentPostprocessing = 0;
306 318 };
3  Example/AEURLExample/AEViewController.m
@@ -10,6 +10,7 @@
10 10 #import "AEURLConnection.h"
11 11 #import "AEJSONProcessor.h"
12 12 #import "AEExpect.h"
  13 +#import "AEURLResponseProcessors.h"
13 14
14 15 @interface AEViewController ()
15 16 @property (nonatomic, retain) NSArray *keys;
@@ -33,7 +34,7 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
33 34 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://graph.facebook.com/137947732957611"]];
34 35 [AEURLConnection sendAsynchronousRequest:request
35 36 queue:[NSOperationQueue mainQueue]
36   - processor:[AEURLConnection chainedResponseProcessor:
  37 + processor:[AEURLResponseProcessors chainedResponseProcessor:
37 38 [AEExpect statusCode:[AEExpect defaultAcceptableStatusCodes]],
38 39 [AEExpect contentType:[AEJSONProcessor defaultAcceptableJSONContentTypes]],
39 40 [AEJSONProcessor JSONResponseProcessor], nil]
12 Example/AEURLExampleTests/AEURLExampleTests.m
@@ -8,6 +8,7 @@
8 8
9 9 #import "AEURLExampleTests.h"
10 10 #import "AEURLConnection.h"
  11 +#import "AEURLRequestFactory.h"
11 12
12 13 // This object simulates UIViewController, which can only be released on the
13 14 // main thread. (This is the root of "The Deallocation Problem"; search the
@@ -62,6 +63,17 @@ - (void)testMainThreadReleaseOnlyObject {
62 63 }
63 64 }
64 65
  66 +- (void)testQueryStringEncode {
  67 + NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:
  68 + @"", @"empty_value",
  69 + @"blah", @"boring_value",
  70 + @"†é߆", @"aççén†é∂", nil];
  71 + NSString *s = AEQueryStringFromParameters(d);
  72 + NSDictionary *d2 = AEParametersFromQueryString(s);
  73 +
  74 + STAssertEqualObjects(d, d2, @"Query string encoding or decoding failed");
  75 +}
  76 +
65 77 @end
66 78
67 79

0 comments on commit 8098542

Please sign in to comment.
Something went wrong with that request. Please try again.