Ver código fonte

Swizzle the NSURLConnection and NSURLSessionTask convenience class methods.

Swizzle all the things. This allows us to hear about requests that don’t make the delegate calls.
Ryan Olson 11 anos atrás
pai
commit
300b68ae83
1 arquivos alterados com 183 adições e 0 exclusões
  1. 183 0
      Classes/Network/PonyDebugger/FLEXNetworkObserver.m

+ 183 - 0
Classes/Network/PonyDebugger/FLEXNetworkObserver.m

@@ -22,6 +22,8 @@
 NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
 NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
 static NSString *const kFLEXNetworkObserverEnableOnLaunchDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
 static NSString *const kFLEXNetworkObserverEnableOnLaunchDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch";
 
 
+typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
+
 @interface FLEXInternalRequestState : NSObject
 @interface FLEXInternalRequestState : NSObject
 
 
 @property (nonatomic, copy) NSURLRequest *request;
 @property (nonatomic, copy) NSURLRequest *request;
@@ -185,6 +187,21 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
     return NO;
     return NO;
 }
 }
 
 
++ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)class withBlock:(id)block swizzledSelector:(SEL)swizzledSelector
+{
+    // This method is only intended for swizzling methods that are know to exist on the class.
+    // Bail if that isn't the case.
+    Method originalMethod = class_getInstanceMethod(class, originalSelector);
+    if (!originalMethod) {
+        return;
+    }
+
+    IMP implementation = imp_implementationWithBlock(block);
+    class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
+    Method newMethod = class_getInstanceMethod(class, swizzledSelector);
+    method_exchangeImplementations(originalMethod, newMethod);
+}
+
 + (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock
 + (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock
 {
 {
     if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
     if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
@@ -269,6 +286,12 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
 
 
         [self injectIntoNSURLConnectionCancel];
         [self injectIntoNSURLConnectionCancel];
         [self injectIntoNSURLSessionTaskResume];
         [self injectIntoNSURLSessionTaskResume];
+
+        [self injectIntoNSURLConnectionAsynchronousClassMethod];
+        [self injectIntoNSURLConnectionSynchronousClassMethod];
+
+        [self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods];
+        [self injectIntoNSURLSessionAsyncUploadTaskMethods];
     });
     });
 }
 }
 
 
@@ -332,6 +355,166 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
     method_exchangeImplementations(originalResume, newResume);
     method_exchangeImplementations(originalResume, newResume);
 }
 }
 
 
++ (void)injectIntoNSURLConnectionAsynchronousClassMethod
+{
+    Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
+    SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:);
+    SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+
+    typedef void (^NSURLConnectionAsyncCompletion)(NSURLResponse* response, NSData* data, NSError* connectionError);
+
+    void (^asyncSwizzleBlock)(Class, NSURLRequest *, NSOperationQueue *, NSURLConnectionAsyncCompletion) = ^(Class slf, NSURLRequest *request, NSOperationQueue *queue, NSURLConnectionAsyncCompletion completion) {
+        NSString *requestID = [self nextRequestID];
+        [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
+        NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+        [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
+        NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
+            [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
+            [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
+            if (connectionError) {
+                [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:connectionError];
+            } else {
+                [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
+            }
+
+            // Call through to the original completion handler
+            if (completion) {
+                completion(response, data, connectionError);
+            }
+        };
+        ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completionWrapper);
+    };
+
+    [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector];
+}
+
++ (void)injectIntoNSURLConnectionSynchronousClassMethod
+{
+    Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
+    SEL selector = @selector(sendSynchronousRequest:returningResponse:error:);
+    SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+
+    NSData *(^syncSwizzleBlock)(Class, NSURLRequest *, NSURLResponse **, NSError **) = ^NSData *(Class slf, NSURLRequest *request, NSURLResponse **response, NSError **error) {
+        NSString *requestID = [self nextRequestID];
+        [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
+        NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+        [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
+        NSError *temporaryError = nil;
+        NSURLResponse *temporaryResponse = nil;
+        NSData *data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, &temporaryResponse, &temporaryError);
+        [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:temporaryResponse];
+        [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
+        if (temporaryError) {
+            [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:temporaryError];
+        } else {
+            [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
+        }
+        if (error) {
+            *error = temporaryError;
+        }
+        if (response) {
+            *response = temporaryResponse;
+        }
+        return data;
+    };
+
+    [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector];
+}
+
++ (void)injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods
+{
+    Class class = [NSURLSession class];
+
+    // The method signatures here are close enough that we can use the same logic to inject into all of them.
+    const SEL selectors[] = {
+        @selector(dataTaskWithHTTPGetRequest:completionHandler:),
+        @selector(dataTaskWithRequest:completionHandler:),
+        @selector(dataTaskWithURL:completionHandler:),
+        @selector(downloadTaskWithRequest:completionHandler:),
+        @selector(downloadTaskWithResumeData:completionHandler:),
+        @selector(downloadTaskWithURL:completionHandler:)
+    };
+
+    const int numSelectors = sizeof(selectors) / sizeof(SEL);
+
+    for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
+        SEL selector = selectors[selectorIndex];
+        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+
+        NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) {
+            NSString *requestID = [self nextRequestID];
+            NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+            NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
+            NSURLSessionTask *task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
+            [self setRequestID:requestID forConnectionOrTask:task];
+            return task;
+        };
+
+        [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector];
+    }
+}
+
++ (void)injectIntoNSURLSessionAsyncUploadTaskMethods
+{
+    Class class = [NSURLSession class];
+
+    // The method signatures here are close enough that we can use the same logic to inject into both of them.
+    // Note that they have 3 arguments, so we can't easily combine with the data and download method above.
+    const SEL selectors[] = {
+        @selector(uploadTaskWithRequest:fromData:completionHandler:),
+        @selector(uploadTaskWithRequest:fromFile:completionHandler:)
+    };
+
+    const int numSelectors = sizeof(selectors) / sizeof(SEL);
+
+    for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
+        SEL selector = selectors[selectorIndex];
+        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+
+        NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) {
+            NSString *requestID = [self nextRequestID];
+            NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+            NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
+            NSURLSessionUploadTask *task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
+            [self setRequestID:requestID forConnectionOrTask:task];
+            return task;
+        };
+
+        [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector];
+    }
+}
+
++ (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class
+{
+    return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
+}
+
++ (NSURLSessionAsyncCompletion)asyncCompletionWrapperForRequestID:(NSString *)requestID mechanism:(NSString *)mechanism completion:(NSURLSessionAsyncCompletion)completion
+{
+    NSURLSessionAsyncCompletion completionWrapper = ^(id fileURLOrData, NSURLResponse *response, NSError *error) {
+        [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
+        [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
+        NSData *data = nil;
+        if ([fileURLOrData isKindOfClass:[NSURL class]]) {
+            data = [NSData dataWithContentsOfURL:fileURLOrData];
+        } else if ([fileURLOrData isKindOfClass:[NSData class]]) {
+            data = fileURLOrData;
+        }
+        [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]];
+        if (error) {
+            [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error];
+        } else {
+            [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
+        }
+
+        // Call through to the original completion handler
+        if (completion) {
+            completion(data, response, error);
+        }
+    };
+    return completionWrapper;
+}
+
 + (void)injectWillSendRequestIntoDelegateClass:(Class)cls
 + (void)injectWillSendRequestIntoDelegateClass:(Class)cls
 {
 {
     SEL selector = @selector(connection:willSendRequest:redirectResponse:);
     SEL selector = @selector(connection:willSendRequest:redirectResponse:);