├── AEURLConnection ├── AEExpect.h ├── AEExpect.m ├── AEJSONProcessor.h ├── AEJSONProcessor.m ├── AEURLConnection.h ├── AEURLConnection.m ├── AEURLRequestFactory.h ├── AEURLRequestFactory.m ├── AEURLRequestOperation.h ├── AEURLRequestOperation.m ├── AEURLResponseProcessors.h └── AEURLResponseProcessors.m ├── Example ├── AEURLExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── AEURLExample │ ├── AEAppDelegate.h │ ├── AEAppDelegate.m │ ├── AEURLExample-Info.plist │ ├── AEURLExample-Prefix.pch │ ├── AEViewController.h │ ├── AEViewController.m │ ├── en.lproj │ │ ├── AEViewController.xib │ │ └── InfoPlist.strings │ └── main.m └── AEURLExampleTests │ ├── AEURLExampleTests-Info.plist │ ├── AEURLExampleTests.h │ ├── AEURLExampleTests.m │ └── en.lproj │ └── InfoPlist.strings ├── LICENSE └── README.md /AEURLConnection/AEExpect.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEExpect.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AEURLConnection.h" 11 | 12 | extern NSString *AEExpectErrorDomain; 13 | 14 | typedef enum { 15 | AEExpectInvalidStatusCodeError = -101, 16 | AEExpectResponseNotHTTPError = -102, 17 | AEExpectInvalidContentTypeError = -103, 18 | AEExpectInvalidResponseClassError = -104, 19 | } AEExpectErrorCode; 20 | 21 | @interface AEExpect : NSObject 22 | 23 | // Sets an error if the HTTP status code is not in the provided set. 24 | + (AEURLResponseProcessor)statusCode:(NSIndexSet *)acceptableCodes; 25 | 26 | // All 200 status codes 27 | + (NSIndexSet *)defaultAcceptableStatusCodes; 28 | 29 | // Sets an error if the Content-Type header does not match one of the included 30 | // acceptable content types, after removing any "charset" or other parameters. 31 | // See [AEJSONProcessor defaultAcceptableJSONContentTypes] for an example set. 32 | + (AEURLResponseProcessor)contentType:(NSSet *)acceptableTypes; 33 | 34 | // Sets an error if the passed data is not an instance of a certain class. 35 | // Handy for use after an AEJSONProcessor, if you want to ensure that 36 | // you're getting a dictionary vs. an array. 37 | + (AEURLResponseProcessor)responseClass:(Class)cl; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /AEURLConnection/AEExpect.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEExpect.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEExpect.h" 10 | 11 | NSString *AEExpectErrorDomain = @"AEExpectErrorDomain"; 12 | 13 | @implementation AEExpect 14 | 15 | + (NSError *)error:(AEExpectErrorCode)code message:(NSString *)message { 16 | return [NSError errorWithDomain:AEExpectErrorDomain 17 | code:code 18 | userInfo:[NSDictionary dictionaryWithObject:message 19 | forKey:NSLocalizedDescriptionKey]]; 20 | } 21 | 22 | + (AEURLResponseProcessor)statusCode:(NSIndexSet *)acceptableCodes { 23 | return [[^id(NSURLResponse *response, id data, NSError **error){ 24 | if (![response isKindOfClass:[NSHTTPURLResponse class]]) { 25 | if (error) { 26 | *error = [AEExpect error:AEExpectResponseNotHTTPError 27 | message:@"Response is not HTTP"]; 28 | } 29 | return nil; 30 | } 31 | 32 | NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode]; 33 | if (![acceptableCodes containsIndex:statusCode]) { 34 | if (error) { 35 | *error = [AEExpect error:AEExpectInvalidStatusCodeError 36 | message:[NSString stringWithFormat:@"%@ (HTTP status %d)", 37 | [NSHTTPURLResponse localizedStringForStatusCode:statusCode], 38 | statusCode]]; 39 | } 40 | return nil; 41 | } 42 | 43 | return data; 44 | } copy] autorelease]; 45 | } 46 | 47 | + (NSIndexSet *)defaultAcceptableStatusCodes { 48 | return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; 49 | } 50 | 51 | // Sets an error if the Content-Type header does not match one of the included 52 | // acceptable content types, after removing any "charset" or other parameters. 53 | + (AEURLResponseProcessor)contentType:(NSSet *)acceptableTypes { 54 | return [[^id(NSURLResponse *response, id data, NSError **error) { 55 | if (![acceptableTypes containsObject:[response MIMEType]]) { 56 | if (error) { 57 | *error = [AEExpect error:AEExpectInvalidContentTypeError 58 | message:[NSString stringWithFormat:@"Invalid Content-Type %@", [response MIMEType]]]; 59 | } 60 | return nil; 61 | } 62 | 63 | return data; 64 | } copy] autorelease]; 65 | } 66 | 67 | // Sets an error if the passed data is not an instance of a certain class. 68 | // Handy for use after an AEJSONProcessor, if you want to ensure that 69 | // you're getting a dictionary vs. an array. 70 | + (AEURLResponseProcessor)responseClass:(Class)cl { 71 | return [[^id(NSURLResponse *response, id data, NSError **error) { 72 | if (![data isKindOfClass:cl]) { 73 | if (error) { 74 | *error = [AEExpect error:AEExpectInvalidResponseClassError 75 | message:[NSString stringWithFormat:@"Invalid response class %@", NSStringFromClass([data class])]]; 76 | } 77 | return nil; 78 | } 79 | 80 | return data; 81 | } copy] autorelease]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /AEURLConnection/AEJSONProcessor.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEJSONProcessor.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AEURLConnection.h" 11 | #import "AEURLRequestFactory.h" 12 | 13 | @interface AEJSONProcessor : NSObject 14 | 15 | // These blocks are used to process a response from the server. 16 | + (AEURLResponseProcessor)JSONResponseProcessor; 17 | + (AEURLResponseProcessor)JSONResponseProcessorWithOptions:(NSJSONReadingOptions)options; 18 | 19 | // This block will put parameters into a NSMutableURLRequest's HTTP body, 20 | // encoded as JSON, and set the request's Content-Type header to 21 | // "application/json; charset=UTF-8". 22 | + (AEURLParameterProcessor)JSONParameterProcessor; 23 | 24 | // A set with the most common Content-Types for JSON. Handy with the 25 | // [AEExpect contentType:] response processor, when used in a chain. 26 | + (NSSet *)defaultAcceptableJSONContentTypes; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /AEURLConnection/AEJSONProcessor.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEJSONProcessor.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEJSONProcessor.h" 10 | 11 | @implementation AEJSONProcessor 12 | 13 | static AEURLResponseProcessor JSONProcessor = nil; 14 | 15 | + (AEURLResponseProcessor)JSONResponseProcessor { 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | JSONProcessor = [[self JSONResponseProcessorWithOptions:0] retain]; 19 | }); 20 | return JSONProcessor; 21 | } 22 | 23 | + (AEURLResponseProcessor)JSONResponseProcessorWithOptions:(NSJSONReadingOptions)options { 24 | return [[(id)^(NSURLResponse *response, NSData *data, NSError **error){ 25 | return [NSJSONSerialization JSONObjectWithData:data options:options error:error]; 26 | } copy] autorelease]; 27 | } 28 | 29 | + (AEURLParameterProcessor)JSONParameterProcessor { 30 | return [[^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){ 31 | NSError *error = nil; 32 | [targetRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error]]; 33 | [targetRequest setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; 34 | } copy] autorelease]; 35 | } 36 | 37 | + (NSSet *)defaultAcceptableJSONContentTypes { 38 | return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLConnection.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AEURLResponseProcessors.h" 11 | 12 | @interface AEURLConnection : NSObject 13 | 14 | // Mirrors the sendAsynchronousRequest:queue:completionHandler: API from iOS 5, 15 | // but is safe for use with iOS 4. |completionHandler| is guaranteed to be 16 | // called *and* released from the context of |queue|. 17 | + (void)sendAsynchronousRequest:(NSURLRequest *)request 18 | queue:(NSOperationQueue*)queue 19 | completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler; 20 | 21 | // You may want to process the response on a background thread, but still safely 22 | // receive the response on |queue|. This method runs |processor| on a 23 | // low-priority serial queue (one operation at a time, to prevent thrashing the 24 | // CPU). completionHandler returns the result of the processing block, instead 25 | // of an NSData* object. 26 | // Check out AEJSONProcessor for an example usage. 27 | + (void)sendAsynchronousRequest:(NSURLRequest *)request 28 | queue:(NSOperationQueue *)queue 29 | processor:(AEURLResponseProcessor)processor 30 | completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLConnection.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEURLConnection.h" 10 | 11 | 12 | @interface AEURLConnectionRequest : NSObject { 13 | id _handler; 14 | } 15 | - (id)initWithRequest:(NSURLRequest *)request 16 | queue:(NSOperationQueue *)queue 17 | processor:(AEURLResponseProcessor)processor 18 | completionHandler:handler; 19 | @property (nonatomic, retain, readonly) NSURLRequest *request; 20 | @property (nonatomic, retain, readonly) NSOperationQueue *queue; 21 | 22 | // processor released in the background, so don't capture a 23 | // UIViewController or you'll be vulnerable to the Deallocation Problem. 24 | @property (nonatomic, copy, readonly) AEURLResponseProcessor processor; 25 | 26 | // handler is readwrite so that we can nil it out after calling it, 27 | // to ensure it is released on |queue| and not on the network thread. 28 | @property (nonatomic, copy, readwrite) id handler; 29 | 30 | @property (nonatomic, retain, readwrite) NSURLConnection *connection; 31 | @property (nonatomic, retain, readwrite) NSURLResponse *response; 32 | @property (nonatomic, retain, readwrite) NSMutableData *data; 33 | @end 34 | 35 | 36 | @interface AEURLConnectionManager : NSObject { 37 | NSThread *_networkRequestThread; 38 | NSMutableArray *_executingRequests; 39 | } 40 | + (AEURLConnectionManager *)sharedManager; 41 | - (void)startRequest:(AEURLConnectionRequest *)req; 42 | @end 43 | 44 | 45 | @implementation AEURLConnection 46 | 47 | + (void)sendAsynchronousRequest:(NSURLRequest *)request 48 | queue:(NSOperationQueue*)queue 49 | completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler { 50 | return [AEURLConnection sendAsynchronousRequest:request queue:queue processor:nil completionHandler:handler]; 51 | } 52 | 53 | + (void)sendAsynchronousRequest:(NSURLRequest *)request 54 | queue:(NSOperationQueue *)queue 55 | processor:(AEURLResponseProcessor)processor 56 | completionHandler:(void (^)(NSURLResponse *, id, NSError *))handler { 57 | AEURLConnectionRequest *req = [[AEURLConnectionRequest alloc] initWithRequest:request queue:queue processor:processor completionHandler:handler]; 58 | [[AEURLConnectionManager sharedManager] startRequest:req]; 59 | [req release]; 60 | } 61 | 62 | @end 63 | 64 | 65 | @implementation AEURLConnectionManager 66 | 67 | static AEURLConnectionManager *sharedManager = nil; 68 | 69 | + (AEURLConnectionManager *)sharedManager { 70 | static dispatch_once_t oncePredicate; 71 | dispatch_once(&oncePredicate, ^{ 72 | sharedManager = [[AEURLConnectionManager alloc] init]; 73 | }); 74 | return sharedManager; 75 | } 76 | 77 | - (void)networkRequestThreadEntryPoint:(id)__unused object { 78 | do { 79 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 80 | [[NSRunLoop currentRunLoop] run]; 81 | [pool drain]; 82 | } while (YES); 83 | } 84 | 85 | - (id)init { 86 | self = [super init]; 87 | if (self) { 88 | _executingRequests = [[NSMutableArray alloc] init]; 89 | 90 | _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; 91 | [_networkRequestThread setThreadPriority:0.1]; 92 | [_networkRequestThread start]; 93 | 94 | } 95 | return self; 96 | } 97 | 98 | - (void)dealloc { 99 | [_networkRequestThread release]; 100 | [_executingRequests release]; 101 | [super dealloc]; 102 | } 103 | 104 | - (void)startRequest:(AEURLConnectionRequest *)req { 105 | // Can be called from any thread. 106 | [self performSelector:@selector(networkThreadStartRequest:) 107 | onThread:_networkRequestThread 108 | withObject:req 109 | waitUntilDone:NO]; 110 | } 111 | 112 | #define EXPECT_NETWORK_THREAD NSAssert([[NSThread currentThread] isEqual:_networkRequestThread], @"Expected network thread") 113 | 114 | - (void)networkThreadStartRequest:(AEURLConnectionRequest *)req { 115 | EXPECT_NETWORK_THREAD; 116 | 117 | // When we get here, |req| should have been freshly created and so its 118 | // |connection|, |response|, and |data| properties should all be nil. 119 | NSAssert([req connection] == nil && [req response] == nil && [req data] == nil, 120 | @"Async request started with invalid state"); 121 | 122 | NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:[req request] 123 | delegate:self 124 | startImmediately:NO]; 125 | [req setConnection:connection]; 126 | [_executingRequests addObject:req]; 127 | 128 | // Now that the request is safely initialized with |connection| and 129 | // stored in |_executingRequests|, start it on the network thread. 130 | [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 131 | [connection start]; 132 | [connection release]; 133 | } 134 | 135 | - (AEURLConnectionRequest *)executingRequestForConnection:(NSURLConnection *)connection { 136 | for (AEURLConnectionRequest *req in _executingRequests) { 137 | if ([req connection] == connection) { 138 | return req; 139 | } 140 | } 141 | NSAssert(false, @"Couldn't find executing request for a given connection"); 142 | return nil; 143 | } 144 | 145 | #pragma mark - NSURLConnectionDelegate 146 | 147 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 148 | EXPECT_NETWORK_THREAD; 149 | 150 | AEURLConnectionRequest *req = [self executingRequestForConnection:connection]; 151 | 152 | // Reset the request's data. From the docs for 153 | // connection:didReceiveResponse:, we read: 154 | 155 | // In rare cases, for example in the case of an HTTP load where the 156 | // content type of the load data is multipart/x-mixed-replace, 157 | // the delegate will receive more than one 158 | // connection:didReceiveResponse: message. In the event this 159 | // occurs, delegates should discard all data previously delivered 160 | // by connection:didReceiveData:, and should be prepared to handle 161 | // the, potentially different, MIME type reported by the newly 162 | // reported URL response. 163 | 164 | [req setData:[NSMutableData data]]; 165 | [req setResponse:response]; 166 | } 167 | 168 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 169 | EXPECT_NETWORK_THREAD; 170 | 171 | AEURLConnectionRequest *req = [self executingRequestForConnection:connection]; 172 | // We strongly expect that [req data] is not nil. However I stop 173 | // short of asserting it, because according to the docs: 174 | 175 | // Zero or more connection:didReceiveResponse: messages will be 176 | // sent to the delegate before receiving a connection:didReceiveData: 177 | // message. The only case where connection:didReceiveResponse: is 178 | // not sent to a delegate is when the protocol implementation 179 | // encounters an error before a response could be created. 180 | 181 | // I am not entirely clear on when you might receive data without 182 | // first receiving a response. If that happens, though, just drop 183 | // the data on the floor; since [req data] is nil, that happens 184 | // automatically. 185 | [[req data] appendData:data]; 186 | } 187 | 188 | - (void)safelyCallCompletionHandler:(AEURLConnectionRequest *)req error:(NSError *)error data:(id)data { 189 | if (error) { 190 | NSAssert(data == nil, @"Didn't expect both error and data"); 191 | } 192 | 193 | // It is very important that |handler| is deallocated in the context of 194 | // |queue|, since doing so has the very nice property of solving the thorny 195 | // Deallocation Problem: 196 | // http://developer.apple.com/library/ios/#technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11 197 | // Two approaches to ensuring this that *don't* work: 198 | // - You might create a block that captures |handler|, separately from 199 | // adding it to the queue, and call setHandler:nil between creating that 200 | // block and adding it to the queue, in an attempt to ensure |handler| 201 | // is deallocated on |queue|. However this won't work since the block you 202 | // create will be stack-allocated, and thus won't retain |handler| 203 | // until you copy the block. When you call setHandler:nil the handler 204 | // will be deallocated, with the stack-allocated block left holding 205 | // a bad reference. 206 | // If you copy the block, though, you'll be right back at square 207 | // one: you have to release it after adding it to the queue, but then 208 | // you race with the block's completion on |queue| for releasing the 209 | // last reference to the block. 210 | // - If you just naively reference [req handler] in the block, |req| itself 211 | // will be captured. Again, however, you need to release |req| after 212 | // either copying the block or adding it to the queue, introducing 213 | // the same race condition. 214 | 215 | // So, create a __block variable with a copy of the handler. This prevents 216 | // any kind of variable capture for |handler| itself. 217 | __block void (^handler)(NSURLResponse *, id, NSError *) = [[req handler] copy]; 218 | // Now call setHandler:nil on |req|. This should release our last retaining 219 | // reference to |handler| EXCEPT for the __block variable. 220 | [req setHandler:nil]; 221 | // Now |handler| is at +1 retain count. We release it on |queue| after 222 | // executing it. That guarantees our last reference is released on |queue|. 223 | 224 | // Note that the block below captures |req|. That is OK since |req| no 225 | // longer has a reference to |handler| (since we called setHandler:nil). 226 | 227 | [[req queue] addOperationWithBlock:^{ 228 | handler([req response], data, error); 229 | [handler release]; 230 | }]; 231 | } 232 | 233 | - (void)executeHandlerForConnection:(NSURLConnection *)connection error:(NSError *)error { 234 | AEURLConnectionRequest *req = [self executingRequestForConnection:connection]; 235 | 236 | if (!error && [req processor]) { 237 | // Create a serial queue to avoid thrashing the CPU. 238 | static dispatch_queue_t processing_queue; 239 | static dispatch_once_t once_token; 240 | dispatch_once(&once_token, ^{ 241 | processing_queue = dispatch_queue_create("com.adamernst.AEURLConnection.processing", 0); 242 | // Don't use DISPATCH_QUEUE_PRIORITY_BACKGROUND as it doesn't exist 243 | // on earlier versions of iOS. 244 | dispatch_set_target_queue(processing_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); 245 | }); 246 | 247 | dispatch_async(processing_queue, ^{ 248 | AEURLResponseProcessor processor = [req processor]; 249 | NSError *processorError = nil; 250 | id processedData = processor([req response], [req data], &processorError); 251 | // Note that the block is required to either return data and leave 252 | // error untouched, or set an error and return nil. This is enforced 253 | // with an assertion in |safelyCallCompletionHandler:error:data:|. 254 | [self safelyCallCompletionHandler:req error:processorError data:processedData]; 255 | }); 256 | } else { 257 | [self safelyCallCompletionHandler:req error:error data:error ? nil : [req data]]; 258 | } 259 | 260 | // Don't remove |req| from |_executingRequests| until this point. Since 261 | // the array is the last retaining reference to |req|, removing it sooner 262 | // will deallocate |req| (and cause us to crash when we try to access its 263 | // properties). 264 | // By this point, we're either done accessing req or it's been captured by 265 | // a block executing asynchronously. 266 | [_executingRequests removeObject:req]; 267 | } 268 | 269 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 270 | EXPECT_NETWORK_THREAD; 271 | [self executeHandlerForConnection:connection error:error]; 272 | } 273 | 274 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 275 | EXPECT_NETWORK_THREAD; 276 | [self executeHandlerForConnection:connection error:nil]; 277 | } 278 | 279 | @end 280 | 281 | 282 | @implementation AEURLConnectionRequest 283 | 284 | @synthesize request=_request; 285 | @synthesize queue=_queue; 286 | @synthesize processor=_processor; 287 | @dynamic handler; 288 | 289 | @synthesize connection=_connection; 290 | @synthesize response=_response; 291 | @synthesize data=_data; 292 | 293 | - (id)initWithRequest:(NSURLRequest *)request 294 | queue:(NSOperationQueue *)queue 295 | processor:(AEURLResponseProcessor)processor 296 | completionHandler:(id)handler { 297 | self = [super init]; 298 | if (self) { 299 | _request = [request retain]; 300 | _queue = [queue retain]; 301 | _processor = [processor copy]; 302 | _handler = [handler copy]; 303 | } 304 | return self; 305 | } 306 | 307 | - (void)dealloc { 308 | [_request release]; 309 | [_queue release]; 310 | [_processor release]; 311 | [_handler release]; 312 | 313 | [_connection release]; 314 | [_response release]; 315 | [_data release]; 316 | 317 | [super dealloc]; 318 | } 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 | 337 | @end 338 | 339 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLRequestFactory.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLRequestFactory.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef id (^AEURLParameterProcessor)(NSDictionary *parameters, NSMutableURLRequest *targetRequest); 12 | 13 | @interface AEURLRequestFactory : NSObject { 14 | NSMutableDictionary *_defaultHeaderValues; 15 | } 16 | 17 | // A singleton request factory. If you set any default header values on this 18 | // factory, they persist to future uses. 19 | + (AEURLRequestFactory *)defaultFactory; 20 | 21 | // This method puts parameters in the query string if method is GET; otherwise, 22 | // it puts them in the HTTP body as x-www-form-urlencoded (just like a browser- 23 | // submitted POST form). 24 | // Need to modify the returned request further? Just use -mutableCopy. 25 | - (NSURLRequest *)requestWithURL:(NSURL *)url 26 | method:(NSString *)method 27 | parameters:(NSDictionary *)parameters; 28 | 29 | // You can pass any block to put the parameters into the generated request: e.g. 30 | // [AEJSONProcessor JSONParameterProcessor], or you could write your own for 31 | // XML, plist, or other encodings. 32 | - (NSURLRequest *)requestWithURL:(NSURL *)url 33 | method:(NSString *)method 34 | parameters:(NSDictionary *)parameters 35 | parameterProcessor:(AEURLParameterProcessor)parameterProcessor; 36 | 37 | - (NSString *)defaultValueForHeader:(NSString *)header; 38 | - (void)setDefaultValue:(NSString *)value forHeader:(NSString *)header; 39 | 40 | // Use this utility functions with setDefaultValue:forHeader:, passing 41 | // "Authorization" for the header. 42 | + (NSString *)authorizationHeaderForUsername:(NSString *)username password:(NSString *)password; 43 | 44 | // A parameter processing block that puts the parameters into the query string 45 | // (usually for GET requests). 46 | + (AEURLParameterProcessor)queryStringProcessor; 47 | 48 | // A parameter processing block that puts the parameters into the HTTP body in 49 | // x-www-form-urlencoded format, like a browser's POST form encoding. 50 | + (AEURLParameterProcessor)formURLEncodedProcessor; 51 | 52 | @end 53 | 54 | // Utility functions to url-encode and -decode values. 55 | NSString *AEURLEncodedStringFromString(NSString *string); 56 | NSString *AEStringFromURLEncodedString(NSString *formEncodedString); 57 | 58 | // Utility function to turn a dictionary into a urlencoded string and vice-versa. 59 | NSString *AEQueryStringFromParameters(NSDictionary *parameters); 60 | NSDictionary *AEParametersFromQueryString(NSString *queryString); 61 | 62 | // Utility functions to base-64 encode/decode data. 63 | NSString *AEBase64EncodedStringFromData(NSData *data); 64 | NSData *AEDataFromBase64EncodedString(NSString *base64); 65 | 66 | // See AEJSONProcessor for a parameter processing block that creates JSON. 67 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLRequestFactory.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLRequestFactory.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/13/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEURLRequestFactory.h" 10 | 11 | @implementation AEURLRequestFactory 12 | 13 | + (AEURLRequestFactory *)defaultFactory { 14 | static AEURLRequestFactory *defaultFactory; 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | defaultFactory = [[AEURLRequestFactory alloc] init]; 18 | }); 19 | return defaultFactory; 20 | } 21 | 22 | - (id)init { 23 | self = [super init]; 24 | if (self) { 25 | _defaultHeaderValues = [[NSMutableDictionary alloc] init]; 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc { 31 | [_defaultHeaderValues release]; 32 | [super dealloc]; 33 | } 34 | 35 | - (NSURLRequest *)requestWithURL:(NSURL *)url 36 | method:(NSString *)method 37 | parameters:(NSDictionary *)parameters { 38 | AEURLParameterProcessor processor = nil; 39 | if ([method isEqualToString:@"GET"]) { 40 | processor = [AEURLRequestFactory queryStringProcessor]; 41 | } else { 42 | processor = [AEURLRequestFactory formURLEncodedProcessor]; 43 | } 44 | return [self requestWithURL:url method:method parameters:parameters parameterProcessor:processor]; 45 | } 46 | 47 | - (NSURLRequest *)requestWithURL:(NSURL *)url 48 | method:(NSString *)method 49 | parameters:(NSDictionary *)parameters 50 | parameterProcessor:(AEURLParameterProcessor)parameterProcessor { 51 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 52 | [request setHTTPMethod:method]; 53 | [request setAllHTTPHeaderFields:_defaultHeaderValues]; 54 | parameterProcessor(parameters, request); 55 | return request; 56 | } 57 | 58 | #pragma mark - Default Header Values 59 | 60 | - (NSString *)defaultValueForHeader:(NSString *)header { 61 | return [_defaultHeaderValues objectForKey:header]; 62 | } 63 | 64 | - (void)setDefaultValue:(NSString *)value forHeader:(NSString *)header { 65 | [_defaultHeaderValues setObject:value forKey:header]; 66 | } 67 | 68 | #pragma mark - Authorization Header Generation 69 | 70 | + (NSString *)authorizationHeaderForUsername:(NSString *)username password:(NSString *)password { 71 | return [NSString stringWithFormat:@"Basic %@", AEBase64EncodedStringFromData([[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding])]; 72 | } 73 | 74 | #pragma mark - Parameter Encoding Blocks 75 | 76 | + (AEURLParameterProcessor)queryStringProcessor { 77 | static AEURLParameterProcessor queryStringProcessor; 78 | static dispatch_once_t onceToken; 79 | dispatch_once(&onceToken, ^{ 80 | queryStringProcessor = [^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){ 81 | NSString *oldURL = [[targetRequest URL] absoluteString]; 82 | NSURL *newURL = [NSURL URLWithString:[oldURL stringByAppendingFormat:[oldURL rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AEQueryStringFromParameters(parameters)]]; 83 | [targetRequest setURL:newURL]; 84 | } copy]; 85 | }); 86 | return queryStringProcessor; 87 | } 88 | 89 | + (AEURLParameterProcessor)formURLEncodedProcessor { 90 | static AEURLParameterProcessor formURLEncodedProcessor; 91 | static dispatch_once_t onceToken; 92 | dispatch_once(&onceToken, ^{ 93 | formURLEncodedProcessor = [^(NSDictionary *parameters, NSMutableURLRequest *targetRequest){ 94 | [targetRequest setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; 95 | [targetRequest setHTTPBody:[AEQueryStringFromParameters(parameters) dataUsingEncoding:NSUTF8StringEncoding]]; 96 | } copy]; 97 | }); 98 | return formURLEncodedProcessor; 99 | } 100 | 101 | @end 102 | 103 | #pragma mark - URLEncoding 104 | 105 | // These functions are based on AFNetworking's equivalents (substituting AE for 106 | // AF in the function prefix to prevent linker conflicts). Thanks AFNetworking! 107 | // (Used with permission.) 108 | 109 | NSString *AEURLEncodedStringFromString(NSString *string) { 110 | static NSString * const kAELegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ "; 111 | 112 | return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAELegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease]; 113 | } 114 | 115 | NSString *AEQueryStringFromParameters(NSDictionary *parameters) { 116 | NSMutableArray *mutableParameterComponents = [NSMutableArray array]; 117 | for (id key in [parameters allKeys]) { 118 | NSString *component = [NSString stringWithFormat:@"%@=%@", AEURLEncodedStringFromString([key description]), AEURLEncodedStringFromString([[parameters valueForKey:key] description])]; 119 | [mutableParameterComponents addObject:component]; 120 | } 121 | 122 | return [mutableParameterComponents componentsJoinedByString:@"&"]; 123 | } 124 | 125 | // AEStringFromURLEncodedString and AEParametersFromQueryString 126 | // are loosely based on Google Web Toolkit. 127 | 128 | NSString *AEStringFromURLEncodedString(NSString *formEncodedString) { 129 | NSMutableString *resultString = [NSMutableString stringWithString:formEncodedString]; 130 | [resultString replaceOccurrencesOfString:@"+" 131 | withString:@" " 132 | options:NSLiteralSearch 133 | range:NSMakeRange(0, [resultString length])]; 134 | return [resultString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 135 | } 136 | 137 | NSDictionary *AEParametersFromQueryString(NSString *queryString) { 138 | NSMutableDictionary *ret = [NSMutableDictionary dictionary]; 139 | NSArray *components = [queryString componentsSeparatedByString:@"&"]; 140 | // Use reverse order so that the first occurrence of a key replaces later ones. 141 | [components enumerateObjectsWithOptions:NSEnumerationReverse 142 | usingBlock:^(id component, NSUInteger idx, BOOL *stop) { 143 | if ([component length] == 0) return; 144 | 145 | NSRange pos = [component rangeOfString:@"="]; 146 | NSString *key; 147 | NSString *val; 148 | if (pos.location == NSNotFound) { 149 | key = AEStringFromURLEncodedString(component); 150 | val = @""; 151 | } else { 152 | key = AEStringFromURLEncodedString([component substringToIndex:pos.location]); 153 | val = AEStringFromURLEncodedString([component substringFromIndex:pos.location + pos.length]); 154 | } 155 | // stringByUnescapingQueryString returns nil on invalid UTF8 156 | // and NSMutableDictionary raises an exception when passed nil values. 157 | if (!key) key = @""; 158 | if (!val) val = @""; 159 | [ret setObject:val forKey:key]; 160 | }]; 161 | return ret; 162 | } 163 | 164 | NSString * AEBase64EncodedStringFromData(NSData *data) { 165 | NSUInteger length = [data length]; 166 | NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 167 | 168 | uint8_t *input = (uint8_t *)[data bytes]; 169 | uint8_t *output = (uint8_t *)[mutableData mutableBytes]; 170 | 171 | for (NSUInteger i = 0; i < length; i += 3) { 172 | NSUInteger value = 0; 173 | for (NSUInteger j = i; j < (i + 3); j++) { 174 | value <<= 8; 175 | if (j < length) { 176 | value |= (0xFF & input[j]); 177 | } 178 | } 179 | 180 | static uint8_t const kAEBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 181 | 182 | NSUInteger idx = (i / 3) * 4; 183 | output[idx + 0] = kAEBase64EncodingTable[(value >> 18) & 0x3F]; 184 | output[idx + 1] = kAEBase64EncodingTable[(value >> 12) & 0x3F]; 185 | output[idx + 2] = (i + 1) < length ? kAEBase64EncodingTable[(value >> 6) & 0x3F] : '='; 186 | output[idx + 3] = (i + 2) < length ? kAEBase64EncodingTable[(value >> 0) & 0x3F] : '='; 187 | } 188 | 189 | return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease]; 190 | } 191 | 192 | NSData *AEDataFromBase64EncodedString(NSString *base64) { 193 | // Adapted from NSData+Base64; Thanks, Matt Gallagher! 194 | // http://cocoawithlove.com/2009/06/base64-encoding-options-on-mac-and.html 195 | const char *input = [base64 cStringUsingEncoding:NSUTF8StringEncoding]; 196 | unsigned long length = strlen(input); 197 | NSMutableData *output = [NSMutableData dataWithCapacity:((length + 3) / 4) * 3]; 198 | 199 | // 200 | // Definition for "masked-out" areas of the base64DecodeLookup mapping 201 | // 202 | static const unsigned char xx = 65; 203 | 204 | // 205 | // Mapping from ASCII character to 6 bit pattern. 206 | // 207 | static unsigned char base64DecodeLookup[256] = 208 | { 209 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 210 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 211 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, 212 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, 213 | xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 214 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, 215 | xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 216 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, 217 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 218 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 219 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 220 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 221 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 222 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 223 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 224 | xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 225 | }; 226 | 227 | size_t i = 0; 228 | size_t j = 0; 229 | while (i < length) 230 | { 231 | // 232 | // Accumulate 4 valid characters (ignore everything else) 233 | // 234 | unsigned char accumulated[4]; 235 | size_t accumulateIndex = 0; 236 | while (i < length) 237 | { 238 | unsigned char decode = base64DecodeLookup[input[i++]]; 239 | if (decode != xx) 240 | { 241 | accumulated[accumulateIndex] = decode; 242 | accumulateIndex++; 243 | 244 | if (accumulateIndex == 4) break; 245 | } 246 | } 247 | 248 | for (int k = 0; k < accumulateIndex - 1; k++) { 249 | uint8_t byte = (accumulated[k] << (k + 1) * 2) | (accumulated[k + 1] >> (2 - k) * 2); 250 | [output appendBytes:&byte length:1]; 251 | } 252 | j += accumulateIndex - 1; 253 | } 254 | 255 | return output; 256 | } 257 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLRequestOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLRequestOperation.h 3 | // Turntable 4 | // 5 | // Created by Adam Ernst on 11/10/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AEURLConnection.h" 11 | 12 | @interface AEURLRequestOperation : NSOperation 13 | 14 | + (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request; 15 | + (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor; 16 | 17 | - (id)initWithRequest:(NSURLRequest *)request; 18 | - (id)initWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor; 19 | 20 | @property (nonatomic, retain, readonly) NSURLRequest *request; 21 | @property (nonatomic, retain, readonly) AEURLResponseProcessor processor; 22 | 23 | @property (nonatomic, retain, readonly) NSURLResponse *response; 24 | @property (nonatomic, retain, readonly) id data; 25 | @property (nonatomic, retain, readonly) NSError *error; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLRequestOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLRequestOperation.m 3 | // Turntable 4 | // 5 | // Created by Adam Ernst on 11/10/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEURLRequestOperation.h" 10 | 11 | typedef enum { 12 | AEURLRequestOperationStateInitial, 13 | AEURLRequestOperationStateExecuting, 14 | AEURLRequestOperationStateFinished, 15 | } AEURLRequestOperationState; 16 | 17 | @interface AEURLRequestOperation () 18 | @property (nonatomic, retain, readwrite) NSURLResponse *response; 19 | @property (nonatomic, retain, readwrite) id data; 20 | @property (nonatomic, retain, readwrite) NSError *error; 21 | 22 | @property (nonatomic) AEURLRequestOperationState state; 23 | @end 24 | 25 | @implementation AEURLRequestOperation 26 | 27 | @synthesize request=_request; 28 | @synthesize processor=_processor; 29 | @synthesize response=_response; 30 | @synthesize data=_data; 31 | @synthesize error=_error; 32 | @synthesize state=_state; 33 | 34 | + (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request { 35 | return [[[AEURLRequestOperation alloc] initWithRequest:request] autorelease]; 36 | } 37 | 38 | + (AEURLRequestOperation *)URLRequestOperationWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor { 39 | return [[[AEURLRequestOperation alloc] initWithRequest:request processor:processor] autorelease]; 40 | } 41 | 42 | - (id)initWithRequest:(NSURLRequest *)request { 43 | return [self initWithRequest:request processor:nil]; 44 | } 45 | 46 | - (id)initWithRequest:(NSURLRequest *)request processor:(AEURLResponseProcessor)processor { 47 | self = [super init]; 48 | if (self) { 49 | _request = [request retain]; 50 | _processor = [processor copy]; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)dealloc { 56 | [_request release]; 57 | [_processor release]; 58 | [_response release]; 59 | [_data release]; 60 | [_error release]; 61 | [super dealloc]; 62 | } 63 | 64 | #pragma mark - State 65 | 66 | - (void)setState:(AEURLRequestOperationState)newState { 67 | AEURLRequestOperationState oldState = _state; 68 | 69 | if ( (newState == AEURLRequestOperationStateExecuting) || (oldState == AEURLRequestOperationStateExecuting) ) { 70 | [self willChangeValueForKey:@"isExecuting"]; 71 | } 72 | if (newState == AEURLRequestOperationStateFinished) { 73 | [self willChangeValueForKey:@"isFinished"]; 74 | } 75 | _state = newState; 76 | if (newState == AEURLRequestOperationStateFinished) { 77 | [self didChangeValueForKey:@"isFinished"]; 78 | } 79 | if ( (newState == AEURLRequestOperationStateExecuting) || (oldState == AEURLRequestOperationStateExecuting) ) { 80 | [self didChangeValueForKey:@"isExecuting"]; 81 | } 82 | } 83 | 84 | - (BOOL)isFinished { 85 | return [self state] == AEURLRequestOperationStateFinished; 86 | } 87 | 88 | - (BOOL)isExecuting { 89 | return [self state] == AEURLRequestOperationStateExecuting; 90 | } 91 | 92 | #pragma mark - NSOperation 93 | 94 | - (BOOL)isConcurrent { 95 | return YES; 96 | } 97 | 98 | - (void)start { 99 | if ([self isCancelled]) { 100 | [self setState:AEURLRequestOperationStateFinished]; 101 | return; 102 | } 103 | 104 | static NSOperationQueue *responseQueue; 105 | static dispatch_once_t onceToken; 106 | dispatch_once(&onceToken, ^{ 107 | responseQueue = [[NSOperationQueue alloc] init]; 108 | }); 109 | 110 | [self setState:AEURLRequestOperationStateExecuting]; 111 | 112 | [AEURLConnection sendAsynchronousRequest:[self request] 113 | queue:responseQueue 114 | processor:[self processor] 115 | completionHandler:^(NSURLResponse *response, id data, NSError *error) { 116 | [self setResponse:response]; 117 | [self setData:data]; 118 | [self setError:error]; 119 | 120 | [self setState:AEURLRequestOperationStateFinished]; 121 | }]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLResponseProcessors.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLResponseProcessors.h 3 | // Turntable 4 | // 5 | // Created by Adam Ernst on 11/10/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // This block can be used as a parameter to |processor:| in AEURLConnection. 12 | // It is REQUIRED that the block either returns an object and leaves the error 13 | // parameter untouched, or sets an error and returns nil. This is enforced with 14 | // an assertion. 15 | typedef id (^AEURLResponseProcessor)(NSURLResponse *, id, NSError **); 16 | 17 | extern NSString *AEURLResponseProcessorsErrorDomain; 18 | 19 | enum { 20 | AEURLResponseProcessorsErrorImageDecodingFailed = -100, 21 | }; 22 | 23 | @interface AEURLResponseProcessors : NSObject 24 | 25 | // A basic response processor that decodes an image, or returns an error. 26 | + (AEURLResponseProcessor)imageResponseProcessor; 27 | 28 | // The real power comes when you chain processors together. This allows 29 | // you to verify that the status code is acceptable, then that the content-type 30 | // is what you expect, then parse JSON, and finally require that the response 31 | // is a dictionary (not an array)--and to do it all in a declarative way. 32 | // This means you can store the chained processor in your app and reuse it in 33 | // different contexts, instead of duplicating the logic everywhere. 34 | 35 | // Note that because completionHandler returns *either* an NSError *or* data, 36 | // never both, you cannot get the document data if a response processor fails. 37 | // If you need to access the HTTP body, you'll need to do your response 38 | // processing the old fashioned way. 39 | 40 | // The returned processor will run each processor in sequence. If one fails by 41 | // returning an NSError*, processing stops immediately and the subsequent 42 | // processors are not run. 43 | // Just like NSArray, *the last argument must be nil.* 44 | + (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... NS_REQUIRES_NIL_TERMINATION; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /AEURLConnection/AEURLResponseProcessors.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLResponseProcessors.m 3 | // Turntable 4 | // 5 | // Created by Adam Ernst on 11/10/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEURLResponseProcessors.h" 10 | 11 | NSString *AEURLResponseProcessorsErrorDomain = @"AEURLResponseProcessorsErrorDomain"; 12 | 13 | @implementation AEURLResponseProcessors 14 | 15 | + (AEURLResponseProcessor)imageResponseProcessor { 16 | return [[^id(NSURLResponse *response, id data, NSError **error) { 17 | UIImage *image = [[[UIImage alloc] initWithData:data] autorelease]; 18 | if (!image) { 19 | if (error) { 20 | *error = [NSError errorWithDomain:AEURLResponseProcessorsErrorDomain 21 | code:AEURLResponseProcessorsErrorImageDecodingFailed 22 | userInfo:nil]; 23 | } 24 | return nil; 25 | } 26 | return image; 27 | } copy] autorelease]; 28 | } 29 | 30 | + (AEURLResponseProcessor)chainedResponseProcessor:(AEURLResponseProcessor)firstProcessor, ... { 31 | NSMutableArray *processors = [NSMutableArray array]; 32 | va_list args; 33 | va_start(args, firstProcessor); 34 | for (AEURLResponseProcessor processor = firstProcessor; processor != nil; processor = va_arg(args, AEURLResponseProcessor)) { 35 | [processors addObject:[[processor copy] autorelease]]; 36 | } 37 | 38 | return [[^id(NSURLResponse *response, id data, NSError **error) { 39 | id newData = data; 40 | for (AEURLResponseProcessor processor in processors) { 41 | newData = processor(response, newData, error); 42 | if (*error) { 43 | NSAssert(newData == nil, @"Expected data or error but not both"); 44 | return nil; 45 | } 46 | } 47 | return newData; 48 | } copy] autorelease]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Example/AEURLExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 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 */; }; 12 | C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB051447332A00228625 /* AEJSONProcessor.m */; }; 13 | C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = C448BB1914473D0A00228625 /* AEURLRequestFactory.m */; }; 14 | C4C186871445FF9C003DBCC0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186861445FF9C003DBCC0 /* UIKit.framework */; }; 15 | C4C186891445FF9C003DBCC0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186881445FF9C003DBCC0 /* Foundation.framework */; }; 16 | C4C1868B1445FF9C003DBCC0 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C1868A1445FF9C003DBCC0 /* CoreGraphics.framework */; }; 17 | C4C186911445FF9C003DBCC0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4C1868F1445FF9C003DBCC0 /* InfoPlist.strings */; }; 18 | C4C186931445FF9C003DBCC0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C186921445FF9C003DBCC0 /* main.m */; }; 19 | C4C186971445FF9C003DBCC0 /* AEAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C186961445FF9C003DBCC0 /* AEAppDelegate.m */; }; 20 | C4C1869A1445FF9C003DBCC0 /* AEViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C186991445FF9C003DBCC0 /* AEViewController.m */; }; 21 | C4C1869D1445FF9C003DBCC0 /* AEViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C4C1869B1445FF9C003DBCC0 /* AEViewController.xib */; }; 22 | C4C186A51445FF9C003DBCC0 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186A41445FF9C003DBCC0 /* SenTestingKit.framework */; }; 23 | C4C186A61445FF9C003DBCC0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186861445FF9C003DBCC0 /* UIKit.framework */; }; 24 | C4C186A71445FF9C003DBCC0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C186881445FF9C003DBCC0 /* Foundation.framework */; }; 25 | C4C186AF1445FF9C003DBCC0 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C4C186AD1445FF9C003DBCC0 /* InfoPlist.strings */; }; 26 | C4C186B21445FF9C003DBCC0 /* AEURLExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C186B11445FF9C003DBCC0 /* AEURLExampleTests.m */; }; 27 | C4C186C31445FFD7003DBCC0 /* AEURLConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */; }; 28 | C4E6E3551447CCCB000AEDA7 /* AEExpect.m in Sources */ = {isa = PBXBuildFile; fileRef = C4E6E3541447CCCB000AEDA7 /* AEExpect.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | C4C186A81445FF9C003DBCC0 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = C4C186791445FF9C003DBCC0 /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = C4C186811445FF9C003DBCC0; 37 | remoteInfo = AEURLExample; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestOperation.h; sourceTree = ""; }; 43 | C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLRequestOperation.m; sourceTree = ""; }; 44 | C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLResponseProcessors.h; sourceTree = ""; }; 45 | C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLResponseProcessors.m; sourceTree = ""; }; 46 | C448BB041447332A00228625 /* AEJSONProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEJSONProcessor.h; sourceTree = ""; }; 47 | C448BB051447332A00228625 /* AEJSONProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEJSONProcessor.m; sourceTree = ""; }; 48 | C448BB1814473D0A00228625 /* AEURLRequestFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLRequestFactory.h; sourceTree = ""; }; 49 | C448BB1914473D0A00228625 /* AEURLRequestFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLRequestFactory.m; sourceTree = ""; }; 50 | C4C186821445FF9C003DBCC0 /* AEURLExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AEURLExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | C4C186861445FF9C003DBCC0 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 52 | C4C186881445FF9C003DBCC0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 53 | C4C1868A1445FF9C003DBCC0 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 54 | C4C1868E1445FF9C003DBCC0 /* AEURLExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AEURLExample-Info.plist"; sourceTree = ""; }; 55 | C4C186901445FF9C003DBCC0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 56 | C4C186921445FF9C003DBCC0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 57 | C4C186941445FF9C003DBCC0 /* AEURLExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AEURLExample-Prefix.pch"; sourceTree = ""; }; 58 | C4C186951445FF9C003DBCC0 /* AEAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AEAppDelegate.h; sourceTree = ""; }; 59 | C4C186961445FF9C003DBCC0 /* AEAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AEAppDelegate.m; sourceTree = ""; }; 60 | C4C186981445FF9C003DBCC0 /* AEViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AEViewController.h; sourceTree = ""; }; 61 | C4C186991445FF9C003DBCC0 /* AEViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AEViewController.m; sourceTree = ""; }; 62 | C4C1869C1445FF9C003DBCC0 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/AEViewController.xib; sourceTree = ""; }; 63 | C4C186A31445FF9C003DBCC0 /* AEURLExampleTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AEURLExampleTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | C4C186A41445FF9C003DBCC0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 65 | C4C186AC1445FF9C003DBCC0 /* AEURLExampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AEURLExampleTests-Info.plist"; sourceTree = ""; }; 66 | C4C186AE1445FF9C003DBCC0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 67 | C4C186B01445FF9C003DBCC0 /* AEURLExampleTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AEURLExampleTests.h; sourceTree = ""; }; 68 | C4C186B11445FF9C003DBCC0 /* AEURLExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AEURLExampleTests.m; sourceTree = ""; }; 69 | C4C186C11445FFD7003DBCC0 /* AEURLConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEURLConnection.h; sourceTree = ""; }; 70 | C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEURLConnection.m; sourceTree = ""; }; 71 | C4E6E3531447CCCB000AEDA7 /* AEExpect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEExpect.h; sourceTree = ""; }; 72 | C4E6E3541447CCCB000AEDA7 /* AEExpect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEExpect.m; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | C4C1867F1445FF9C003DBCC0 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | C4C186871445FF9C003DBCC0 /* UIKit.framework in Frameworks */, 81 | C4C186891445FF9C003DBCC0 /* Foundation.framework in Frameworks */, 82 | C4C1868B1445FF9C003DBCC0 /* CoreGraphics.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | C4C1869F1445FF9C003DBCC0 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | C4C186A51445FF9C003DBCC0 /* SenTestingKit.framework in Frameworks */, 91 | C4C186A61445FF9C003DBCC0 /* UIKit.framework in Frameworks */, 92 | C4C186A71445FF9C003DBCC0 /* Foundation.framework in Frameworks */, 93 | ); 94 | runOnlyForDeploymentPostprocessing = 0; 95 | }; 96 | /* End PBXFrameworksBuildPhase section */ 97 | 98 | /* Begin PBXGroup section */ 99 | C4C186771445FF9C003DBCC0 = { 100 | isa = PBXGroup; 101 | children = ( 102 | C4C186BF1445FFCB003DBCC0 /* AEURLConnection */, 103 | C4C1868C1445FF9C003DBCC0 /* AEURLExample */, 104 | C4C186AA1445FF9C003DBCC0 /* AEURLExampleTests */, 105 | C4C186851445FF9C003DBCC0 /* Frameworks */, 106 | C4C186831445FF9C003DBCC0 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | usesTabs = 0; 110 | }; 111 | C4C186831445FF9C003DBCC0 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | C4C186821445FF9C003DBCC0 /* AEURLExample.app */, 115 | C4C186A31445FF9C003DBCC0 /* AEURLExampleTests.octest */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | C4C186851445FF9C003DBCC0 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | C4C186861445FF9C003DBCC0 /* UIKit.framework */, 124 | C4C186881445FF9C003DBCC0 /* Foundation.framework */, 125 | C4C1868A1445FF9C003DBCC0 /* CoreGraphics.framework */, 126 | C4C186A41445FF9C003DBCC0 /* SenTestingKit.framework */, 127 | ); 128 | name = Frameworks; 129 | sourceTree = ""; 130 | }; 131 | C4C1868C1445FF9C003DBCC0 /* AEURLExample */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | C4C186951445FF9C003DBCC0 /* AEAppDelegate.h */, 135 | C4C186961445FF9C003DBCC0 /* AEAppDelegate.m */, 136 | C4C186981445FF9C003DBCC0 /* AEViewController.h */, 137 | C4C186991445FF9C003DBCC0 /* AEViewController.m */, 138 | C4C1869B1445FF9C003DBCC0 /* AEViewController.xib */, 139 | C4C1868D1445FF9C003DBCC0 /* Supporting Files */, 140 | ); 141 | path = AEURLExample; 142 | sourceTree = ""; 143 | }; 144 | C4C1868D1445FF9C003DBCC0 /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | C4C1868E1445FF9C003DBCC0 /* AEURLExample-Info.plist */, 148 | C4C1868F1445FF9C003DBCC0 /* InfoPlist.strings */, 149 | C4C186921445FF9C003DBCC0 /* main.m */, 150 | C4C186941445FF9C003DBCC0 /* AEURLExample-Prefix.pch */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | C4C186AA1445FF9C003DBCC0 /* AEURLExampleTests */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | C4C186B01445FF9C003DBCC0 /* AEURLExampleTests.h */, 159 | C4C186B11445FF9C003DBCC0 /* AEURLExampleTests.m */, 160 | C4C186AB1445FF9C003DBCC0 /* Supporting Files */, 161 | ); 162 | path = AEURLExampleTests; 163 | sourceTree = ""; 164 | }; 165 | C4C186AB1445FF9C003DBCC0 /* Supporting Files */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | C4C186AC1445FF9C003DBCC0 /* AEURLExampleTests-Info.plist */, 169 | C4C186AD1445FF9C003DBCC0 /* InfoPlist.strings */, 170 | ); 171 | name = "Supporting Files"; 172 | sourceTree = ""; 173 | }; 174 | C4C186BF1445FFCB003DBCC0 /* AEURLConnection */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | C4C186C11445FFD7003DBCC0 /* AEURLConnection.h */, 178 | C4C186C21445FFD7003DBCC0 /* AEURLConnection.m */, 179 | C448BB041447332A00228625 /* AEJSONProcessor.h */, 180 | C448BB051447332A00228625 /* AEJSONProcessor.m */, 181 | C448BB1814473D0A00228625 /* AEURLRequestFactory.h */, 182 | C448BB1914473D0A00228625 /* AEURLRequestFactory.m */, 183 | C4E6E3531447CCCB000AEDA7 /* AEExpect.h */, 184 | C4E6E3541447CCCB000AEDA7 /* AEExpect.m */, 185 | C42113D414A4D012006E19D2 /* AEURLRequestOperation.h */, 186 | C42113D514A4D012006E19D2 /* AEURLRequestOperation.m */, 187 | C42113D614A4D012006E19D2 /* AEURLResponseProcessors.h */, 188 | C42113D714A4D012006E19D2 /* AEURLResponseProcessors.m */, 189 | ); 190 | name = AEURLConnection; 191 | path = ../AEURLConnection; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | C4C186811445FF9C003DBCC0 /* AEURLExample */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = C4C186B51445FF9C003DBCC0 /* Build configuration list for PBXNativeTarget "AEURLExample" */; 200 | buildPhases = ( 201 | C4C1867E1445FF9C003DBCC0 /* Sources */, 202 | C4C1867F1445FF9C003DBCC0 /* Frameworks */, 203 | C4C186801445FF9C003DBCC0 /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = AEURLExample; 210 | productName = AEURLExample; 211 | productReference = C4C186821445FF9C003DBCC0 /* AEURLExample.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | C4C186A21445FF9C003DBCC0 /* AEURLExampleTests */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = C4C186B81445FF9C003DBCC0 /* Build configuration list for PBXNativeTarget "AEURLExampleTests" */; 217 | buildPhases = ( 218 | C4C1869E1445FF9C003DBCC0 /* Sources */, 219 | C4C1869F1445FF9C003DBCC0 /* Frameworks */, 220 | C4C186A01445FF9C003DBCC0 /* Resources */, 221 | C4C186A11445FF9C003DBCC0 /* ShellScript */, 222 | ); 223 | buildRules = ( 224 | ); 225 | dependencies = ( 226 | C4C186A91445FF9C003DBCC0 /* PBXTargetDependency */, 227 | ); 228 | name = AEURLExampleTests; 229 | productName = AEURLExampleTests; 230 | productReference = C4C186A31445FF9C003DBCC0 /* AEURLExampleTests.octest */; 231 | productType = "com.apple.product-type.bundle"; 232 | }; 233 | /* End PBXNativeTarget section */ 234 | 235 | /* Begin PBXProject section */ 236 | C4C186791445FF9C003DBCC0 /* Project object */ = { 237 | isa = PBXProject; 238 | attributes = { 239 | LastUpgradeCheck = 0420; 240 | ORGANIZATIONNAME = cosmicsoft; 241 | }; 242 | buildConfigurationList = C4C1867C1445FF9C003DBCC0 /* Build configuration list for PBXProject "AEURLExample" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | ); 249 | mainGroup = C4C186771445FF9C003DBCC0; 250 | productRefGroup = C4C186831445FF9C003DBCC0 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | C4C186811445FF9C003DBCC0 /* AEURLExample */, 255 | C4C186A21445FF9C003DBCC0 /* AEURLExampleTests */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | C4C186801445FF9C003DBCC0 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | C4C186911445FF9C003DBCC0 /* InfoPlist.strings in Resources */, 266 | C4C1869D1445FF9C003DBCC0 /* AEViewController.xib in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | C4C186A01445FF9C003DBCC0 /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | C4C186AF1445FF9C003DBCC0 /* InfoPlist.strings in Resources */, 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | /* End PBXResourcesBuildPhase section */ 279 | 280 | /* Begin PBXShellScriptBuildPhase section */ 281 | C4C186A11445FF9C003DBCC0 /* ShellScript */ = { 282 | isa = PBXShellScriptBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | inputPaths = ( 287 | ); 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | C4C1867E1445FF9C003DBCC0 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | C4C186931445FF9C003DBCC0 /* main.m in Sources */, 302 | C4C186971445FF9C003DBCC0 /* AEAppDelegate.m in Sources */, 303 | C4C1869A1445FF9C003DBCC0 /* AEViewController.m in Sources */, 304 | C4C186C31445FFD7003DBCC0 /* AEURLConnection.m in Sources */, 305 | C448BB061447332A00228625 /* AEJSONProcessor.m in Sources */, 306 | C448BB1A14473D0A00228625 /* AEURLRequestFactory.m in Sources */, 307 | C4E6E3551447CCCB000AEDA7 /* AEExpect.m in Sources */, 308 | C42113D814A4D012006E19D2 /* AEURLRequestOperation.m in Sources */, 309 | C42113D914A4D012006E19D2 /* AEURLResponseProcessors.m in Sources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | C4C1869E1445FF9C003DBCC0 /* Sources */ = { 314 | isa = PBXSourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | C4C186B21445FF9C003DBCC0 /* AEURLExampleTests.m in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXSourcesBuildPhase section */ 322 | 323 | /* Begin PBXTargetDependency section */ 324 | C4C186A91445FF9C003DBCC0 /* PBXTargetDependency */ = { 325 | isa = PBXTargetDependency; 326 | target = C4C186811445FF9C003DBCC0 /* AEURLExample */; 327 | targetProxy = C4C186A81445FF9C003DBCC0 /* PBXContainerItemProxy */; 328 | }; 329 | /* End PBXTargetDependency section */ 330 | 331 | /* Begin PBXVariantGroup section */ 332 | C4C1868F1445FF9C003DBCC0 /* InfoPlist.strings */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | C4C186901445FF9C003DBCC0 /* en */, 336 | ); 337 | name = InfoPlist.strings; 338 | sourceTree = ""; 339 | }; 340 | C4C1869B1445FF9C003DBCC0 /* AEViewController.xib */ = { 341 | isa = PBXVariantGroup; 342 | children = ( 343 | C4C1869C1445FF9C003DBCC0 /* en */, 344 | ); 345 | name = AEViewController.xib; 346 | sourceTree = ""; 347 | }; 348 | C4C186AD1445FF9C003DBCC0 /* InfoPlist.strings */ = { 349 | isa = PBXVariantGroup; 350 | children = ( 351 | C4C186AE1445FF9C003DBCC0 /* en */, 352 | ); 353 | name = InfoPlist.strings; 354 | sourceTree = ""; 355 | }; 356 | /* End PBXVariantGroup section */ 357 | 358 | /* Begin XCBuildConfiguration section */ 359 | C4C186B31445FF9C003DBCC0 /* Debug */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ALWAYS_SEARCH_USER_PATHS = NO; 363 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 364 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 365 | COPY_PHASE_STRIP = NO; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 374 | GCC_VERSION = ""; 375 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 377 | GCC_WARN_UNUSED_VARIABLE = YES; 378 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 379 | SDKROOT = iphoneos; 380 | }; 381 | name = Debug; 382 | }; 383 | C4C186B41445FF9C003DBCC0 /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ALWAYS_SEARCH_USER_PATHS = NO; 387 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_OPTIMIZATION_LEVEL = 3; 392 | GCC_VERSION = ""; 393 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 397 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 398 | SDKROOT = iphoneos; 399 | VALIDATE_PRODUCT = YES; 400 | }; 401 | name = Release; 402 | }; 403 | C4C186B61445FF9C003DBCC0 /* Debug */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 407 | GCC_PREFIX_HEADER = "AEURLExample/AEURLExample-Prefix.pch"; 408 | INFOPLIST_FILE = "AEURLExample/AEURLExample-Info.plist"; 409 | PRODUCT_NAME = "$(TARGET_NAME)"; 410 | WRAPPER_EXTENSION = app; 411 | }; 412 | name = Debug; 413 | }; 414 | C4C186B71445FF9C003DBCC0 /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 418 | GCC_PREFIX_HEADER = "AEURLExample/AEURLExample-Prefix.pch"; 419 | INFOPLIST_FILE = "AEURLExample/AEURLExample-Info.plist"; 420 | PRODUCT_NAME = "$(TARGET_NAME)"; 421 | WRAPPER_EXTENSION = app; 422 | }; 423 | name = Release; 424 | }; 425 | C4C186B91445FF9C003DBCC0 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/AEURLExample.app/AEURLExample"; 429 | FRAMEWORK_SEARCH_PATHS = ( 430 | "$(SDKROOT)/Developer/Library/Frameworks", 431 | "$(DEVELOPER_LIBRARY_DIR)/Frameworks", 432 | ); 433 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 434 | GCC_PREFIX_HEADER = "AEURLExample/AEURLExample-Prefix.pch"; 435 | INFOPLIST_FILE = "AEURLExampleTests/AEURLExampleTests-Info.plist"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | TEST_HOST = "$(BUNDLE_LOADER)"; 438 | WRAPPER_EXTENSION = octest; 439 | }; 440 | name = Debug; 441 | }; 442 | C4C186BA1445FF9C003DBCC0 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/AEURLExample.app/AEURLExample"; 446 | FRAMEWORK_SEARCH_PATHS = ( 447 | "$(SDKROOT)/Developer/Library/Frameworks", 448 | "$(DEVELOPER_LIBRARY_DIR)/Frameworks", 449 | ); 450 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 451 | GCC_PREFIX_HEADER = "AEURLExample/AEURLExample-Prefix.pch"; 452 | INFOPLIST_FILE = "AEURLExampleTests/AEURLExampleTests-Info.plist"; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | TEST_HOST = "$(BUNDLE_LOADER)"; 455 | WRAPPER_EXTENSION = octest; 456 | }; 457 | name = Release; 458 | }; 459 | /* End XCBuildConfiguration section */ 460 | 461 | /* Begin XCConfigurationList section */ 462 | C4C1867C1445FF9C003DBCC0 /* Build configuration list for PBXProject "AEURLExample" */ = { 463 | isa = XCConfigurationList; 464 | buildConfigurations = ( 465 | C4C186B31445FF9C003DBCC0 /* Debug */, 466 | C4C186B41445FF9C003DBCC0 /* Release */, 467 | ); 468 | defaultConfigurationIsVisible = 0; 469 | defaultConfigurationName = Release; 470 | }; 471 | C4C186B51445FF9C003DBCC0 /* Build configuration list for PBXNativeTarget "AEURLExample" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | C4C186B61445FF9C003DBCC0 /* Debug */, 475 | C4C186B71445FF9C003DBCC0 /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | C4C186B81445FF9C003DBCC0 /* Build configuration list for PBXNativeTarget "AEURLExampleTests" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | C4C186B91445FF9C003DBCC0 /* Debug */, 484 | C4C186BA1445FF9C003DBCC0 /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | /* End XCConfigurationList section */ 490 | }; 491 | rootObject = C4C186791445FF9C003DBCC0 /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /Example/AEURLExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEAppDelegate.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AEViewController; 12 | 13 | @interface AEAppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | @property (strong, nonatomic) AEViewController *viewController; 17 | @property (strong, nonatomic) UINavigationController *navigationController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEAppDelegate.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEAppDelegate.h" 10 | 11 | #import "AEViewController.h" 12 | 13 | @implementation AEAppDelegate 14 | 15 | @synthesize window = _window; 16 | @synthesize viewController = _viewController; 17 | @synthesize navigationController = _navigationController; 18 | 19 | - (void)dealloc { 20 | [_window release]; 21 | [_viewController release]; 22 | [_navigationController release]; 23 | [super dealloc]; 24 | } 25 | 26 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 27 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 28 | self.viewController = [[[AEViewController alloc] initWithNibName:@"AEViewController" bundle:nil] autorelease];; 29 | self.navigationController = [[[UINavigationController alloc] initWithRootViewController:self.viewController] autorelease]; 30 | self.window.rootViewController = self.navigationController; 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEURLExample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.adamernst.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEURLExample-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AEURLExample' target in the 'AEURLExample' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEViewController.h 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AEViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/AEURLExample/AEViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEViewController.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEViewController.h" 10 | #import "AEURLConnection.h" 11 | #import "AEJSONProcessor.h" 12 | #import "AEExpect.h" 13 | #import "AEURLResponseProcessors.h" 14 | 15 | @interface AEViewController () 16 | @property (nonatomic, retain) NSArray *keys; 17 | @property (nonatomic, retain) NSDictionary *result; 18 | @end 19 | 20 | @implementation AEViewController 21 | 22 | @synthesize keys=_keys; 23 | @synthesize result=_result; 24 | 25 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { 26 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 27 | if (self) { 28 | UIActivityIndicatorView *spinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease]; 29 | 30 | [self setTitle:@"AEURL Example"]; 31 | [[self navigationItem] setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithCustomView:spinner] autorelease]]; 32 | [spinner startAnimating]; 33 | 34 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://graph.facebook.com/137947732957611"]]; 35 | [AEURLConnection sendAsynchronousRequest:request 36 | queue:[NSOperationQueue mainQueue] 37 | processor:[AEURLResponseProcessors chainedResponseProcessor: 38 | [AEExpect statusCode:[AEExpect defaultAcceptableStatusCodes]], 39 | [AEExpect contentType:[AEJSONProcessor defaultAcceptableJSONContentTypes]], 40 | [AEJSONProcessor JSONResponseProcessor], nil] 41 | completionHandler:^(NSURLResponse *response, id data, NSError *error) { 42 | [spinner stopAnimating]; 43 | 44 | if (error) { 45 | [[[[UIAlertView alloc] initWithTitle:[error localizedDescription] 46 | message:[error localizedRecoverySuggestion] 47 | delegate:nil 48 | cancelButtonTitle:@"OK" 49 | otherButtonTitles:nil] autorelease] show]; 50 | } else { 51 | [self setKeys:[data allKeys]]; 52 | [self setResult:data]; 53 | [[self tableView] reloadData]; 54 | } 55 | }]; 56 | } 57 | return self; 58 | } 59 | 60 | - (void)dealloc { 61 | [_keys release]; 62 | [_result release]; 63 | [super dealloc]; 64 | } 65 | 66 | #pragma mark - UITableViewDelegate/UITableViewDataSource 67 | 68 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 69 | return [[self keys] count]; 70 | } 71 | 72 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 73 | id key = [[self keys] objectAtIndex:[indexPath row]]; 74 | id value = [[self result] objectForKey:key]; 75 | 76 | UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:nil] autorelease]; 77 | [[cell textLabel] setText:[key description]]; 78 | [[cell detailTextLabel] setText:[value description]]; 79 | return cell; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Example/AEURLExample/en.lproj/AEViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C73 6 | 1938 7 | 1138.23 8 | 567.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 933 12 | 13 | 14 | IBProxyObject 15 | IBUITableView 16 | 17 | 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | PluginDependencyRecalculationVersion 22 | 23 | 24 | 25 | 26 | IBFilesOwner 27 | IBCocoaTouchFramework 28 | 29 | 30 | IBFirstResponder 31 | IBCocoaTouchFramework 32 | 33 | 34 | 35 | 274 36 | {320, 460} 37 | 38 | 39 | _NS:418 40 | 41 | 10 42 | 43 | 549453824 44 | {512, 1} 45 | 46 | 47 | 48 | 49 | 50 | TU0AKgAACAjFzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ 51 | y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ 52 | xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ 53 | xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ 54 | xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ 55 | xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/ 56 | xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/ 57 | y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ 58 | y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ 59 | xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ 60 | xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ 61 | xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ 62 | xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/ 63 | xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/ 64 | y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ 65 | y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ 66 | xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ 67 | xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ 68 | xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ 69 | xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/ 70 | xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/ 71 | y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ 72 | y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ 73 | xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ 74 | xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ 75 | xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ 76 | xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/ 77 | xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/ 78 | y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/ 79 | y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/ 80 | xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/ 81 | xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/ 82 | xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/xczS/8vS2P/L0tj/xczU/8XM1P/FzNT/ 83 | xczU/8XM0v/L0tj/y9LY/8XM1P/FzNT/xczU/8XM1P/FzNL/y9LY/8vS2P/FzNT/xczU/8XM1P/FzNT/ 84 | xczS/8vS2P/L0tj/xczU/wANAQAAAwAAAAECAAAAAQEAAwAAAAEAAQAAAQIAAwAAAAQAAAiqAQMAAwAA 85 | AAEAAQAAAQYAAwAAAAEAAgAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEABAAAARYAAwAA 86 | AAEAAQAAARcABAAAAAEAAAgAARwAAwAAAAEAAQAAAVIAAwAAAAEAAQAAAVMAAwAAAAQAAAiyAAAAAAAI 87 | AAgACAAIAAEAAQABAAE 88 | 89 | 90 | 91 | 92 | 93 | 3 94 | MCAwAA 95 | 96 | 97 | groupTableViewBackgroundColor 98 | 99 | YES 100 | IBCocoaTouchFramework 101 | YES 102 | 1 103 | 2 104 | 0 105 | YES 106 | 44 107 | 10 108 | 10 109 | 110 | 111 | 112 | 113 | 114 | 115 | view 116 | 117 | 118 | 119 | 9 120 | 121 | 122 | 123 | dataSource 124 | 125 | 126 | 127 | 10 128 | 129 | 130 | 131 | delegate 132 | 133 | 134 | 135 | 11 136 | 137 | 138 | 139 | 140 | 141 | 0 142 | 143 | 144 | 145 | 146 | 147 | -1 148 | 149 | 150 | File's Owner 151 | 152 | 153 | -2 154 | 155 | 156 | 157 | 158 | 8 159 | 160 | 161 | 162 | 163 | 164 | 165 | AEViewController 166 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 167 | UIResponder 168 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 169 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 170 | 171 | 172 | 173 | 174 | 175 | 11 176 | 177 | 178 | 179 | 180 | AEViewController 181 | UITableViewController 182 | 183 | IBProjectSource 184 | ./Classes/AEViewController.h 185 | 186 | 187 | 188 | 189 | 0 190 | IBCocoaTouchFramework 191 | YES 192 | 3 193 | 933 194 | 195 | 196 | -------------------------------------------------------------------------------- /Example/AEURLExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/AEURLExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AEURLExample 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AEAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AEAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/AEURLExampleTests/AEURLExampleTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.adamernst.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/AEURLExampleTests/AEURLExampleTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLExampleTests.h 3 | // AEURLExampleTests 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AEURLExampleTests : SenTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/AEURLExampleTests/AEURLExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AEURLExampleTests.m 3 | // AEURLExampleTests 4 | // 5 | // Created by Adam Ernst on 10/12/11. 6 | // Copyright (c) 2011 cosmicsoft. All rights reserved. 7 | // 8 | 9 | #import "AEURLExampleTests.h" 10 | #import "AEURLConnection.h" 11 | #import "AEURLRequestFactory.h" 12 | 13 | // This object simulates UIViewController, which can only be released on the 14 | // main thread. (This is the root of "The Deallocation Problem"; search the 15 | // web for more.) 16 | @interface AEMainThreadReleaseOnlyObject : NSObject { 17 | BOOL *gotResponse; 18 | BOOL *dealloced; 19 | BOOL *deallocedOnMainThread; 20 | } 21 | - (id)initWithGotResponse:(BOOL *)g dealloced:(BOOL *)d onMainThread:(BOOL *)main; 22 | - (void)handleResponse:(NSURLResponse *)response; 23 | @end 24 | 25 | @implementation AEURLExampleTests 26 | 27 | - (void)testMainThreadReleaseOnlyObject { 28 | NSAssert([NSThread isMainThread], @"Expected unit test to run on main thread"); 29 | 30 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com/"] 31 | cachePolicy:NSURLRequestUseProtocolCachePolicy 32 | timeoutInterval:20.0]; 33 | 34 | BOOL gotResponse = NO; 35 | BOOL dealloced = NO; 36 | BOOL deallocedOnMainThread = NO; 37 | AEMainThreadReleaseOnlyObject *obj; 38 | obj = [[AEMainThreadReleaseOnlyObject alloc] initWithGotResponse:&gotResponse 39 | dealloced:&dealloced 40 | onMainThread:&deallocedOnMainThread]; 41 | 42 | [AEURLConnection sendAsynchronousRequest:request 43 | queue:[NSOperationQueue mainQueue] 44 | completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 45 | [obj handleResponse:response]; 46 | }]; 47 | [obj release]; 48 | NSAssert(!dealloced, @"Object shouldn't be dealloced yet; should be dealloced during running of runloop"); 49 | 50 | int runs = 0; 51 | while (dealloced == NO && runs < 20) { 52 | NSLog(@"Running main run loop since object isn't deallocated yet"); 53 | [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; 54 | runs++; 55 | } 56 | 57 | if (!gotResponse) { 58 | STFail(@"Completion handler never called, although timeout should have expired so we should have received response or error"); 59 | } else if (!dealloced) { 60 | STFail(@"Object captured in completion handler was never released"); 61 | } else if (!deallocedOnMainThread) { 62 | STFail(@"Main-thread-release-only object was dealloced, but not on main thread"); 63 | } 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 | 77 | - (void)testBase64 { 78 | static const int kTestLength = 2043; 79 | NSMutableData *testData = [NSMutableData dataWithCapacity:kTestLength]; 80 | srand(494020102); 81 | for (int i = 0; i < kTestLength / 4; i++) { 82 | u_int32_t randomBits = rand(); 83 | [testData appendBytes:(void*)&randomBits length:4]; 84 | } 85 | 86 | NSString *base64Value = AEBase64EncodedStringFromData(testData); 87 | NSData *decodedValue = AEDataFromBase64EncodedString(base64Value); 88 | STAssertEqualObjects(testData, decodedValue, @"Base 64 encoding or decoding failed"); 89 | } 90 | 91 | @end 92 | 93 | 94 | @implementation AEMainThreadReleaseOnlyObject 95 | 96 | - (id)initWithGotResponse:(BOOL *)g dealloced:(BOOL *)d onMainThread:(BOOL *)main { 97 | if (self = [super init]) { 98 | gotResponse = g; 99 | dealloced = d; 100 | deallocedOnMainThread = main; 101 | } 102 | return self; 103 | } 104 | 105 | - (void)dealloc { 106 | *deallocedOnMainThread = [NSThread isMainThread]; 107 | *dealloced = YES; 108 | [super dealloc]; 109 | } 110 | 111 | - (void)handleResponse:(NSURLResponse *)response { 112 | *gotResponse = YES; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Example/AEURLExampleTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Adam Ernst (http://www.adamernst.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AEURLConnection # 2 | ## Effortless, safe block-based URL requests ## 3 | 4 | iOS 5 introduces [sendAsynchronousRequest:queue:completionHandler:](http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/clm/NSURLConnection/sendAsynchronousRequest:queue:completionHandler:), 5 | a great new API that makes it easy to dispatch a `NSURLRequest` and safely 6 | receive a callback when it finishes. 7 | 8 | `AEURLConnection` is a simple reimplementation of the API for use on iOS 4. 9 | Used properly, it is also guaranteed to be safe against [The Deallocation Problem](http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11), 10 | a thorny threading issue that affects most other networking libraries. 11 | 12 | ## How do I use it? ## 13 | 14 | [AEURLConnection sendAsynchronousRequest:request 15 | queue:[NSOperationQueue mainQueue] 16 | completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { 17 | // Handle the response, or error. 18 | }]; 19 | 20 | ## What's this "Deallocation Problem"? ## 21 | 22 | [Read up on it here.](http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11) 23 | If you are making asynchronous network requests from a `UIViewController`, 24 | your app almost certainly **will crash** under certain circumstances. (Of 25 | course, you shouldn't be calling the network from `UIViewController` if 26 | you're implementing MVC properly, but that's another story!) Here's a 27 | short summary: 28 | 29 | 1. UIViewController **must** be deallocated on the main thread. 30 | 2. Depending on how you are issuing asynchronous network requests, it is 31 | likely that your `UIViewController` is being retained by a background 32 | thread. 33 | * If you're using `-performSelectorInBackground:withObject:` and then 34 | calling `+sendSynchronousRequest:returningResponse:error:`, you are 35 | spawning a background thread that retains `UIViewController` since it 36 | is the target of the invocation. 37 | * If you're using `NSOperation` in any way—e.g. 38 | [ASIHTTPRequest](http://allseeing-i.com/ASIHTTPRequest/) or 39 | `AFHTTPRequestOperation` from 40 | [AFNetworking](https://github.com/gowalla/AFNetworking)—you're almost 41 | certainly retaining your `UIViewController`, unless you have total 42 | separation between the controller and a model layer that never lets 43 | the controller see a secondary thread. If you set a `completionBlock` 44 | on an operation that references the view controller, or reference the 45 | view controller from an AFNetworking success/failure block, you're 46 | retaining the controller on a background thread. 47 | 3. If the background thread is the last object to release your 48 | `UIViewController`, your app will crash. 49 | * This can happen if the user pops a view controller (by tapping the 50 | back button) before a running operation completes. 51 | * To see it in action, open your app on a slow network connection. Open 52 | a view that loads data from the network, then immediately press back. 53 | If you're vulnerable, your app will crash. 54 | 55 | It's a nasty problem that's extremely difficult to work around. If you're 56 | using an `NSOperation`, the only way to prevent it is to: 57 | 58 | * Never reference `self` or any ivars in the completion block 59 | * Create a `__block id blockSelf` variable to refer to `self`, like so: 60 | 61 | block id blockSelf = [self retain]; 62 | [myOperation setCompletionBlock:^{ 63 | dispatch_async(dispatch_get_main_queue(), ^{ 64 | [blockSelf operationFinishedWithData:[myOperation data]]; 65 | [blockSelf release]; 66 | } 67 | // Prevent retain cycle since completionBlock references 68 | // myOperation 69 | [myOperation setCompletionBlock:nil]; 70 | }]; 71 | 72 | Or, the simpler option: **don't use `NSOperation` at all**. Instead use 73 | `AEURLConnection`. 74 | 75 | ## How does AEURLConnection solve the problem? ## 76 | 77 | First, it allows you to specify a queue that you want to receive the response 78 | on, instead of giving it to you on a random background thread. Most of the 79 | time you'll want to specify `[NSOperationQueue mainQueue]`, which will execute 80 | the completion handler on the main thread. 81 | 82 | Second, the `completionHandler` block is guaranteed to be *released* on that 83 | same queue. This means you can capture `UIViewControllers` willy-nilly without 84 | worrying; the `completionHandler`, and thus all the view controllers it 85 | captures, will safely be released on the main thread. 86 | 87 | ## When should I use an `NSOperation`? ## 88 | 89 | You might need to use an `NSOperation` if: 90 | 91 | 1. You need to limit the number of requests being issued simultaneously. 92 | 2. You need the ability to cancel a request, or get the request progress. 93 | 3. You need to download large files. `NSURLConnectionDownloadDelegate` provides 94 | a better solution for this, but it's iOS 5 only. 95 | 96 | I'm working on a solution for number 2 that allows you to pass an 97 | `options` dictionary with blocks for progress updates, and returning an object 98 | to the caller that can be canceled. 99 | --------------------------------------------------------------------------------