Reputation: 8636
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
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:
Upvotes: 0
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:
Upvotes: 2
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