xplat
xplat

Reputation: 8636

Swizzle of NSURLConnection sendAsynchronous:request:queue:completionHandler and sendSynchronousRequest:returningResponse:error: not working

I have created an NSURLConnection and NSURLSession categories to swizzle so I will intercept calls in runtime and gather network information. Everything works great in the most part except when I use the static class methods of NSURLConnection

sendAsynchronousRequest:queue:completionHandler:
sendSynchronousRequest:returningResponse:error:

These two static methods just don't obey in my swizzling, and while debugging I see that the method implementation exchange is happening properly as other methods I swizzle. Here is my code similar to what I do to other methods that seems to work pretty good.

typedef void (^SendAsynchronousCompletionHandlerBlock)(NSURLResponse*, NSData*, NSError*);

static void (*OriginalNSURLConnectionSendAsynchronousRequestQueueCompletionHandler)(id, SEL, NSURLRequest*, NSOperationQueue*, SendAsynchronousCompletionHandlerBlock);
static NSData* (*OriginalNSURLConnectionSendSynchronousRequestReturningResponseError)(id, SEL, NSURLRequest*, NSURLResponse**, NSError**);

static void MyNSURLConnectionSendAsynchronousRequestQueueCompletionHandler(id self, SEL _cmd, NSURLRequest* request, NSOperationQueue* queue, SendAsynchronousCompletionHandlerBlock completionHandler)
    {
    NSLog(@"Implementation Intercept in %s", __PRETTY_FUNCTION__);

    OriginalNSURLConnectionSendAsynchronousRequestQueueCompletionHandler(self, _cmd, request, queue, completionHandler);
    }

static NSData* MyNSURLConnectionSendSynchronousRequestReturningResponseError(id self, SEL _cmd, NSURLRequest* request, NSURLResponse** response, NSError** error)
    {
    NSLog(@"Implementation Intercept in %s", __PRETTY_FUNCTION__);

    NSData* data = OriginalNSURLConnectionSendSynchronousRequestReturningResponseError(self, _cmd, request, response, error);

    return data;
    }

 @implementation NSURLConnection (MyNSURLConnection)

+ (void) load
    {
    // Create onceToken
    static dispatch_once_t onceToken;
    // Use dispatch_once to make sure this runs only once in the lifecycle
    dispatch_once(&onceToken,
        ^{
        NSLog(@"Injecting code to NSURLConnection");
        [self injectImplementationToNSURLConnectionSendAsynchronousRequestQueueCompletionHandler];
        [self injectImplementationToNSURLConnectionSendSynchronousRequestReturningResponseError];

        // Some other methods I intercept, just as reference, they work as tested to init an NSURLConnection object
        // I will skip their implementation which is similar to what I show here
        [self injectImplementationToNSURLConnectionConnectionWithRequestDelegate];
        [self injectImplementationToNSURLConnectionInitWithRequestDelegateStartImmediately];
        [self injectImplementationToNSURLConnectionInitWithRequestDelegate];
        [self injectImplementationToNSURLConnectionStart];
        });
    }

+ (void) injectImplementationToNSURLConnectionSendAsynchronousRequestQueueCompletionHandler
    {
    // Replace the method on the same class that's used
    // in the calling code
    Class class =  [NSURLConnection class];

    // The Original +sendAsynchronousRequest:queue:completionHandler:
    SEL originalSelector = @selector(sendAsynchronousRequest:queue:completionHandler:);

    // The Replacement method implementation
    IMP replacement = (IMP)MyNSURLConnectionSendAsynchronousRequestQueueCompletionHandler;

    // This will eventually hold the original sendAsynchronousRequest:queue:completionHandler:
    IMP* store = (IMP*)&OriginalNSURLConnectionSendAsynchronousRequestQueueCompletionHandler;

    IMP originalImp = NULL;
    Method method = class_getClassMethod(class, originalSelector);
    if (method)
        {
        const char* type = method_getTypeEncoding(method);
        // Replace the original method with the MyNSURLConnectionSendAsynchronousRequestQueueCompletionHandler
        originalImp = class_replaceMethod(class, originalSelector, replacement, type);
        if (!originalImp)
            {
            originalImp = method_getImplementation(method);
            }
        }

    // Put the original method IMP into the pointer
    if (originalImp && store)
        {
        *store = originalImp;
        }
    }

+ (void) injectImplementationToNSURLConnectionSendSynchronousRequestReturningResponseError
    {
    // Replace the method on the same class that's used
    // in the calling code
    Class class =  [NSURLConnection class];

    // The Original +sendSynchronousRequest:returningResponse:error: selector
    SEL originalSelector = @selector(sendSynchronousRequest:returningResponse:error:);

    // The Replacement method implementation
    IMP replacement = (IMP)MyNSURLConnectionSendSynchronousRequestReturningResponseError;

    // This will eventually hold the original sendSynchronousRequest:returningResponse:error:
    IMP* store = (IMP*)&OriginalNSURLConnectionSendSynchronousRequestReturningResponseError;

    IMP originalImp = NULL;
    Method method = class_getClassMethod(class, originalSelector);
    if (method)
        {
        const char* type = method_getTypeEncoding(method);
        // Replace the original method with the MyNSURLConnectionSendSynchronousRequestReturningResponseError
        originalImp = class_replaceMethod(class, originalSelector, replacement, type);
        if (!originalImp)
            {
            originalImp = method_getImplementation(method);
            }
        }

    // Put the original method IMP into the pointer
    if (originalImp && store)
        {
        *store = originalImp;
        }
    }

So what makes different these methods that I can't have my code swizzle into the original implementation, and with no any error occur in the way.

Here is the code that I test it with:

- (IBAction) executeURLRequest: (UIButton*)sender
    {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://My.URL/file.json"]];
    [request setValue:@"API_KEY" forHTTPHeaderField:@"X-My-Auth-Token"];
    NSURLResponse* response;
    NSError* error;
    // Doesn't work, my swizzle method is not invoked
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    // Doesn't work, my swizzle method is not invoked
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:
        ^(NSURLResponse *response, NSData *data, NSError *error)
        {
        if (error) NSLog(@"NSURLConnection failed: %@", [error debugDescription]);
        NSLog(@"Made the NSURLRequest to My");
        }];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
        ^{
        // It works, I get to see my message of the method invoked to the output console
        NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:nil startImmediately:YES];
        });
    }

I don't know, it looks pretty OK to me...what do you think?

Upvotes: 1

Views: 1870

Answers (3)

jqyao
jqyao

Reputation: 170

Here is my code and I am able to swizzle the (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue )queue completionHandler:(void (^)(NSURLResponse, NSData*, NSError*))handler

// + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
void (*gOrigNSURLConnection_sendAsynchronousRequestQueueCompletionHandler)(id,SEL,NSURLRequest *,NSOperationQueue *,DataTaskCompletionBlock) = NULL;


static void NSURLConnection_logTelemetrySendAsynchronousRequestQueueCompletionHandler(id self, SEL _cmd, NSURLRequest* request, NSOperationQueue* queue, DataTaskCompletionBlock completionHandler)
{
    NSLog(@"i am catched");
    return gOrigNSURLConnection_sendAsynchronousRequestQueueCompletionHandler( self, _cmd ,request,queue,completionHandler);
}

// in swizzling method 
// NSURLConnection  + (void)sendAsynchronousRequest:queue:completionHandler:

    selMethod = @selector(sendAsynchronousRequest:queue:completionHandler:);
    impOverrideMethod = (IMP) NSURLConnection_logTelemetrySendAsynchronousRequestQueueCompletionHandler;
    origMethod = class_getClassMethod(c,selMethod);
    gOrigNSURLConnection_sendAsynchronousRequestQueueCompletionHandler = (void *)method_getImplementation(origMethod);

    if( gOrigNSURLConnection_sendAsynchronousRequestQueueCompletionHandler != NULL )
    {
        method_setImplementation(origMethod, impOverrideMethod);
        ++numSwizzledMethods;
    } else {
        NSLog(@"error: unable to swizzle + (void)sendAsynchronousRequest:queue:completionHandler:");
    }

My testing code

- (IBAction)clickSendAsyncWithBlock:(id)sender {
    NSURL *URL = [NSURL URLWithString:@"http://localhost:8000/a.txt"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];

    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                               NSLog(@"got %@" , myString);
                           }];
}

Result:

  • 2014-06-17 10:31:29.086 TelemetrySwizzling[3017:60b] i am catched
  • 2014-06-17 10:31:29.103 TelemetrySwizzling[3017:60b] got a

Upvotes: 0

Paul Dardeau
Paul Dardeau

Reputation: 2619

Although you need to be extra careful with swizzling, I'm unaware of any policies that Apple has that forbids swizzling of system frameworks. I've swizzled these same classes myself and the SDK has been included in apps in the app store.

NSURLConnection is not implemented as a class cluster, although NSURLSession is.

I authored the code that swizzles NSURLConnection and NSURLSession within the Apigee iOS SDK. Look here for the implementation of the swizzling of NSURLConnection:

https://github.com/apigee/apigee-ios-sdk/blob/master/source/Classes/Services/NSURLConnection%2BApigee.m

Upvotes: 2

bbum
bbum

Reputation: 162722

First, don't swizzle system framework methods outside of debugging and/or learning purposes. It is fragile, will break over releases of the OS, and apps that swizzle system frameworks will not be approved or removed when discovered.

Secondly, it is most likely the case that NSURLConnection is implemented as a class cluster. Thus, there is likely some subclass that implements the actual connection and you are swizzling the abstract super's implementation, which does nothing.

Upvotes: 2

Related Questions