3

How can I implement -[NSURLConnection sendAsynchronousRequest:queue:completionHandler:] for iOS < 5?

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0

#import <objc/runtime.h>
#import "NSURLConnection+iOS4.h"

// Dynamically add -[NSURLConnection sendAsynchronousRequest:queue:completionHandler:].
void *sendAsynchronousRequest4(id self, SEL _cmd, NSURLRequest *request, NSOperationQueue *queue, void (^handler)(NSURLResponse*, NSData*, NSError*));
void *sendAsynchronousRequest4(id self, SEL _cmd, NSURLRequest *request, NSOperationQueue *queue, void (^handler)(NSURLResponse*, NSData*, NSError*)) {

    // How should we implement this?

}

@implementation NSURLConnection (SendAsync)

+ (void)load {
    SEL sendAsyncSelector = @selector(sendAsynchronousRequest:queue:completionHandler:);
    if (![NSURLConnection instancesRespondToSelector:]) {
        class_addMethod([self class], sendAsyncSelector, (IMP)sendAsynchronousRequest4, "v@:@@@");
    }
}

@end

#endif
ma11hew28
  • 121,420
  • 116
  • 450
  • 651

3 Answers3

3
// NSURLConnection+SendAsync.h

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0

#import <Foundation/Foundation.h>

@interface NSURLConnection (SendAsync)

@end

#endif


// NSURLConnection+SendAsync.m

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0

typedef void (^URLConnectionCompletionHandler)(NSURLResponse *response, NSData *data, NSError *error);

@interface URLConnectionDelegate : NSObject <NSURLConnectionDataDelegate>

@property (nonatomic, strong) NSURLResponse *response;
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, copy) URLConnectionCompletionHandler handler;

@end 

@implementation URLConnectionDelegate 

@synthesize response;
@synthesize data;
@synthesize queue;
@synthesize handler;

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)theResponse {
    self.response = theResponse;
    [data setLength:0]; // reset data
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData {
    [data appendData:theData]; // append incoming data
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    self.data = nil;
    if (handler) { [queue addOperationWithBlock:^{ handler(response, nil, error); }]; }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    // TODO: Are we passing the arguments to the block correctly? Should we copy them?
    if (handler) { [queue addOperationWithBlock:^{ handler(response, data, nil); }]; }
}

@end

#import <objc/runtime.h>
#import "NSURLConnection+SendAsync.h"

// Dynamically add @property (nonatomic,readonly) UIViewController *presentingViewController.
void sendAsynchronousRequest4(id self, SEL _cmd, NSURLRequest *request, NSOperationQueue *queue,
                              URLConnectionCompletionHandler handler);
void sendAsynchronousRequest4(id self, SEL _cmd, NSURLRequest *request, NSOperationQueue *queue,
                              URLConnectionCompletionHandler handler) {

    URLConnectionDelegate *connectionDelegate = [[URLConnectionDelegate alloc] init];
    connectionDelegate.data = [NSMutableData data];
    connectionDelegate.queue = queue;
    connectionDelegate.handler = handler;
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:request
                                                                delegate:connectionDelegate];
    NSAssert(connection, nil);
}

@implementation NSURLConnection (SendAsync)

+ (void)load {
    SEL sendAsyncSelector = @selector(sendAsynchronousRequest:queue:completionHandler:);
    if (![NSURLConnection instancesRespondToSelector:sendAsyncSelector]) {
        class_addMethod(object_getClass([self class]),
                        sendAsyncSelector, (IMP)sendAsynchronousRequest4, "v@:@@@");
    }
}

@end

#endif
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Hey Matt, is there a version of this code somewhere on github or similar? It would be nice to have it somewhere where people can easily submit bug fixes and improvements – Andrea Jul 20 '12 at 22:57
  • Instead of `instancesRespondToSelector`, shouldn't we be using `respondsToSelector`? We are checking for the presence of the class method. – adbie Jan 08 '13 at 09:33
0

I typically make a sub-class of NSOperation and do the sync request inside the main method of the operation. To invoke the operation I create an instance of the sub-class and throw it onto a queue (not the main thread). Then when the operation completes it either calls a delegate with the received data or posts a notification via NSNotificationCenter.

Jack Cox
  • 3,290
  • 24
  • 25
-1

I would go with an already built framework like ASIHTTPRequest (No longer being developed, but still very good) or RestKit (I've never used it before but heard it's decent). These will give you the same functionality (asyncrhonous http requests) across different versions of the OS.

ifdefs aren't the way to go since they're done at compile time and you won't be compiling separate versions of the app for each platform.

You could probably do some shenanigans at runtime to get it to work but it seems like more trouble than it's worth.

Randall
  • 14,691
  • 7
  • 40
  • 60