2014-06-14 28 views
1

我创建了一个NSURLConnectionNSURLSession类别,以便在调试时拦截呼叫并收集网络信息。 一切在大多数情况下,除了当我使用的NSURLConnectionSwizzle NSURLConnection sendAsynchronous:request:queue:completionHandler and sendSynchronousRequest:returningResponse:error:not working

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

这两个静态方法静态类方法的伟大工程,只是,虽然调试我看到这个方法的实现交换正在发生的事情不要在我混写服从正如其他方法,我swizzle。 这里是我的代码,类似于我对其他方法似乎工作得很好。

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; 
     } 
    } 

那么是什么让不同的这些方法,我不能有我的代码调配成原来的执行,并没有出现在任何方式错误。

这里是我测试的代码:

- (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]; 
     }); 
    } 

我不知道,它看起来相当好,我...你有什么感想?

回答

0

这里是我的代码,我能调配的(无效)sendAsynchronousRequest:(的NSURLRequest *)请求队列(NSOperationQueue )队列completionHandler: (无效(^)(NSURLResponse,NSData的*,NSError *))处理器

// + (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:"); 
    } 

我的测试代码

- (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); 
          }]; 
} 

结果:

  • 2014年6月17日10:31:29.086 TelemetrySwizzling [3017:60B]我钓到
  • 2014年6月17日10时31分29秒。103 TelemetrySwizzling [3017:60b]得到了一个
+0

是的,这个实现工作,我可以看到你的代码和我的区别是,你得到原始的实现与gOrigNSURLConnection_sendAsynchronousRequestQueueCompletionHandler =(void *)method_getImplementation(origMethod);我为同步版本做了同样的工作,它也可以工作。因为现在所有其他部分按预期工作,所以我现在将继续执行其余的实施。如果可能的话,请为我的知识库解释。 –

2

首先,除调试和/或学习目的外,不要调试系统框架方法。它很脆弱,会破坏操作系统的版本,并且调试系统框架的应用程序在发现时不会被批准或删除。

其次,NSURLConnection很可能是作为类集群实现的。因此,可能有一些子类实现了实际的连接,并且您正在调整抽象超级实现,它什么都不做。

+0

除了调试和实验学习,在我看来,另一种情况是,在单元测试中可能是合理的swizzle框架方法。 – jlehr

1

虽然您需要特别小心,但我并不知道苹果有任何禁止混搭系统框架的政策。我自己调试了这些相同的类,SDK已包含在应用商店的应用中。

尽管NSURLSession是NSURLConnection,但它并未作为类集群实现。

我编写了在Apigee iOS SDK中调试NSURLConnection和NSURLSession的代码。看看这里NSURLConnection的的混写的实现:

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

+0

嗨保罗,谢谢你,很好的实施!苹果公司没有任何混乱的限制,iOS 7和iOS 8 SDK也充斥着它。我发现你不能调用我想要的方法,并为开发人员创建自己的方法包装器。你有没有发现为什么? –

+0

我已经测试了Apigee框架,并且我遇到了同样的行为,您正在记录sendSynchronous(我没有原因),但异步版本也不是从Apigee SDK记录的。 –

相关问题