2014-06-14 27 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記錄的。 –

相關問題