Ver código fonte

Protect NSURLSession and NSURLConnection swizzling with dispatch_once

Ryan Olson 11 anos atrás
pai
commit
1f43569a4d
1 arquivos alterados com 172 adições e 154 exclusões
  1. 172 154
      Classes/Network/PonyDebugger/FLEXNetworkObserver.m

+ 172 - 154
Classes/Network/PonyDebugger/FLEXNetworkObserver.m

@@ -321,205 +321,223 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
 
 + (void)injectIntoNSURLConnectionCancel
 {
-    Class class = [NSURLConnection class];
-    SEL selector = @selector(cancel);
-    SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
-    Method originalCancel = class_getInstanceMethod(class, selector);
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class class = [NSURLConnection class];
+        SEL selector = @selector(cancel);
+        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+        Method originalCancel = class_getInstanceMethod(class, selector);
 
-    void (^swizzleBlock)(NSURLConnection *) = ^(NSURLConnection *slf) {
-        [[FLEXNetworkObserver sharedObserver] connectionWillCancel:slf];
-        ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
-    };
+        void (^swizzleBlock)(NSURLConnection *) = ^(NSURLConnection *slf) {
+            [[FLEXNetworkObserver sharedObserver] connectionWillCancel:slf];
+            ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
+        };
 
-    IMP implementation = imp_implementationWithBlock(swizzleBlock);
-    class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalCancel));
-    Method newCancel = class_getInstanceMethod(class, swizzledSelector);
-    method_exchangeImplementations(originalCancel, newCancel);
+        IMP implementation = imp_implementationWithBlock(swizzleBlock);
+        class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalCancel));
+        Method newCancel = class_getInstanceMethod(class, swizzledSelector);
+        method_exchangeImplementations(originalCancel, newCancel);
+    });
 }
 
 + (void)injectIntoNSURLSessionTaskResume
 {
-    Class class = [NSURLSessionTask class];
-    SEL selector = @selector(resume);
-    SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class class = [NSURLSessionTask class];
+        SEL selector = @selector(resume);
+        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
 
-    if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
-        // Dummy NSURLSessionTask to get the actual class, needed for iOS 7 (__NSCFURLSessionTask)
-        class = [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"about:blank"]] superclass];
-    }
+        if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
+            // Dummy NSURLSessionTask to get the actual class, needed for iOS 7 (__NSCFURLSessionTask)
+            class = [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"about:blank"]] superclass];
+        }
 
-    Method originalResume = class_getInstanceMethod(class, selector);
+        Method originalResume = class_getInstanceMethod(class, selector);
 
-    void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) {
-        [[FLEXNetworkObserver sharedObserver] URLSessionTaskWillResume:slf];
-        ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
-    };
+        void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) {
+            [[FLEXNetworkObserver sharedObserver] URLSessionTaskWillResume:slf];
+            ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector);
+        };
 
-    IMP implementation = imp_implementationWithBlock(swizzleBlock);
-    class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalResume));
-    Method newResume = class_getInstanceMethod(class, swizzledSelector);
-    method_exchangeImplementations(originalResume, newResume);
+        IMP implementation = imp_implementationWithBlock(swizzleBlock);
+        class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalResume));
+        Method newResume = class_getInstanceMethod(class, swizzledSelector);
+        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);
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class class = objc_getMetaClass(class_getName([NSURLConnection class]));
+        SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:);
+        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
 
-    void (^asyncSwizzleBlock)(Class, NSURLRequest *, NSOperationQueue *, NSURLConnectionAsyncCompletion) = ^(Class slf, NSURLRequest *request, NSOperationQueue *queue, NSURLConnectionAsyncCompletion completion) {
-        if ([FLEXNetworkObserver isEnabled]) {
-            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];
-                }
+        typedef void (^NSURLConnectionAsyncCompletion)(NSURLResponse* response, NSData* data, NSError* connectionError);
 
-                // 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);
-        } else {
-            ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completion);
-        }
-    };
+        void (^asyncSwizzleBlock)(Class, NSURLRequest *, NSOperationQueue *, NSURLConnectionAsyncCompletion) = ^(Class slf, NSURLRequest *request, NSOperationQueue *queue, NSURLConnectionAsyncCompletion completion) {
+            if ([FLEXNetworkObserver isEnabled]) {
+                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];
+                    }
 
-    [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector];
+                    // 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);
+            } else {
+                ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completion);
+            }
+        };
+        
+        [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];
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        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) {
-        NSData *data = nil;
-        if ([FLEXNetworkObserver isEnabled]) {
-            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;
-            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];
+        NSData *(^syncSwizzleBlock)(Class, NSURLRequest *, NSURLResponse **, NSError **) = ^NSData *(Class slf, NSURLRequest *request, NSURLResponse **response, NSError **error) {
+            NSData *data = nil;
+            if ([FLEXNetworkObserver isEnabled]) {
+                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;
+                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;
+                }
             } else {
-                [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data];
-            }
-            if (error) {
-                *error = temporaryError;
+                data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, response, error);
             }
-            if (response) {
-                *response = temporaryResponse;
-            }
-        } else {
-            data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, response, error);
-        }
-
-        return data;
-    };
 
-    [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector];
+            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:)
-    };
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class class = [NSURLSession class];
 
-    const int numSelectors = sizeof(selectors) / sizeof(SEL);
+        // 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:)
+        };
 
-    for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
-        SEL selector = selectors[selectorIndex];
-        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+        const int numSelectors = sizeof(selectors) / sizeof(SEL);
 
-        if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
-            // iOS 7 does not implement these methods on NSURLSession. We actually want to
-            // swizzle __NSCFURLSession, which we can get from the class of the shared session
-            class = [[NSURLSession sharedSession] class];
-        }
+        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) {
-            NSURLSessionTask *task = nil;
-            if ([FLEXNetworkObserver isEnabled]) {
-                NSString *requestID = [self nextRequestID];
-                NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
-                NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
-                task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
-                [self setRequestID:requestID forConnectionOrTask:task];
-            } else {
-                task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completion);
+            if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
+                // iOS 7 does not implement these methods on NSURLSession. We actually want to
+                // swizzle __NSCFURLSession, which we can get from the class of the shared session
+                class = [[NSURLSession sharedSession] class];
             }
-            return task;
-        };
 
-        [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector];
-    }
+            NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) {
+                NSURLSessionTask *task = nil;
+                if ([FLEXNetworkObserver isEnabled]) {
+                    NSString *requestID = [self nextRequestID];
+                    NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+                    NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
+                    task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
+                    [self setRequestID:requestID forConnectionOrTask:task];
+                } else {
+                    task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completion);
+                }
+                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:)
-    };
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Class class = [NSURLSession class];
 
-    const int numSelectors = sizeof(selectors) / sizeof(SEL);
+        // 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:)
+        };
 
-    for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) {
-        SEL selector = selectors[selectorIndex];
-        SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
+        const int numSelectors = sizeof(selectors) / sizeof(SEL);
 
-        if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
-            // iOS 7 does not implement these methods on NSURLSession. We actually want to
-            // swizzle __NSCFURLSession, which we can get from the class of the shared session
-            class = [[NSURLSession sharedSession] class];
-        }
+        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) {
-            NSURLSessionUploadTask *task = nil;
-            if ([FLEXNetworkObserver isEnabled]) {
-                NSString *requestID = [self nextRequestID];
-                NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
-                NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
-                task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
-                [self setRequestID:requestID forConnectionOrTask:task];
-            } else {
-                task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completion);
+            if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
+                // iOS 7 does not implement these methods on NSURLSession. We actually want to
+                // swizzle __NSCFURLSession, which we can get from the class of the shared session
+                class = [[NSURLSession sharedSession] class];
             }
-            return task;
-        };
 
-        [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector];
-    }
+            NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) {
+                NSURLSessionUploadTask *task = nil;
+                if ([FLEXNetworkObserver isEnabled]) {
+                    NSString *requestID = [self nextRequestID];
+                    NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
+                    NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
+                    task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
+                    [self setRequestID:requestID forConnectionOrTask:task];
+                } else {
+                    task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completion);
+                }
+                return task;
+            };
+            
+            [self replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector];
+        }
+    });
 }
 
 + (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class