// // FLEXNetworkObserver.m // Derived from: // // PDAFNetworkDomainController.m // PonyDebugger // // Created by Mike Lewis on 2/27/12. // // Licensed to Square, Inc. under one or more contributor license agreements. // See the LICENSE file distributed with this work for the terms under // which Square, Inc. licenses this file to you. // #import "FLEXNetworkObserver.h" #import "FLEXNetworkRecorder.h" #import "FLEXUtility.h" #import #import #import NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification"; static NSString *const kFLEXNetworkObserverEnabledDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch"; typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error); @interface FLEXInternalRequestState : NSObject @property (nonatomic, copy) NSURLRequest *request; @property (nonatomic) NSMutableData *dataAccumulator; @end @implementation FLEXInternalRequestState @end @interface FLEXNetworkObserver (NSURLConnectionHelpers) - (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response delegate:(id )delegate; - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id )delegate; - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id )delegate; - (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id )delegate; - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id )delegate; - (void)connectionWillCancel:(NSURLConnection *)connection; @end @interface FLEXNetworkObserver (NSURLSessionTaskHelpers) - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id )delegate; - (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id )delegate; - (void)URLSessionTaskWillResume:(NSURLSessionTask *)task; @end @interface FLEXNetworkObserver () @property (nonatomic) NSMutableDictionary *requestStatesForRequestIDs; @property (nonatomic) dispatch_queue_t queue; @end @implementation FLEXNetworkObserver #pragma mark - Public Methods + (void)setEnabled:(BOOL)enabled { BOOL previouslyEnabled = [self isEnabled]; [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:kFLEXNetworkObserverEnabledDefaultsKey]; if (enabled) { // Inject if needed. This injection is protected with a dispatch_once, so we're ok calling it multiple times. // By doing the injection lazily, we keep the impact of the tool lower when this feature isn't enabled. [self injectIntoAllNSURLConnectionDelegateClasses]; } if (previouslyEnabled != enabled) { [NSNotificationCenter.defaultCenter postNotificationName:kFLEXNetworkObserverEnabledStateChangedNotification object:self]; } } + (BOOL)isEnabled { return [[[NSUserDefaults standardUserDefaults] objectForKey:kFLEXNetworkObserverEnabledDefaultsKey] boolValue]; } + (void)load { // We don't want to do the swizzling from +load because not all the classes may be loaded at this point. dispatch_async(dispatch_get_main_queue(), ^{ if ([self isEnabled]) { [self injectIntoAllNSURLConnectionDelegateClasses]; } }); } #pragma mark - Statics + (instancetype)sharedObserver { static FLEXNetworkObserver *sharedObserver = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedObserver = [self new]; }); return sharedObserver; } + (NSString *)nextRequestID { return [[NSUUID UUID] UUIDString]; } #pragma mark Delegate Injection Convenience Methods /// All swizzled delegate methods should make use of this guard. /// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled. /// The superclass implementation (and implementations in classes above that) will be executed without interference if called from the original implementation. + (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock { // If we don't have an object to detect nested calls on, just run the original implementation and bail. // This case can happen if someone besides the URL loading system calls the delegate methods directly. // See https://github.com/Flipboard/FLEX/issues/61 for an example. if (!object) { originalImplementationBlock(); return; } const void *key = selector; // Don't run the sniffing block if we're inside a nested call if (!objc_getAssociatedObject(object, key)) { sniffingBlock(); } // Mark that we're calling through to the original so we can detect nested calls objc_setAssociatedObject(object, key, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); originalImplementationBlock(); objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - Delegate Injection + (void)injectIntoAllNSURLConnectionDelegateClasses { // Only allow swizzling once. static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Swizzle any classes that implement one of these selectors. const SEL selectors[] = { @selector(connectionDidFinishLoading:), @selector(connection:willSendRequest:redirectResponse:), @selector(connection:didReceiveResponse:), @selector(connection:didReceiveData:), @selector(connection:didFailWithError:), @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:), @selector(URLSession:dataTask:didReceiveData:), @selector(URLSession:dataTask:didReceiveResponse:completionHandler:), @selector(URLSession:task:didCompleteWithError:), @selector(URLSession:dataTask:didBecomeDownloadTask:), @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:), @selector(URLSession:downloadTask:didFinishDownloadingToURL:) }; const int numSelectors = sizeof(selectors) / sizeof(SEL); Class *classes = NULL; int numClasses = objc_getClassList(NULL, 0); if (numClasses > 0) { classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); for (NSInteger classIndex = 0; classIndex < numClasses; ++classIndex) { Class class = classes[classIndex]; if (class == [FLEXNetworkObserver class]) { continue; } // Use the runtime API rather than the methods on NSObject to avoid sending messages to // classes we're not interested in swizzling. Otherwise we hit +initialize on all classes. // NOTE: calling class_getInstanceMethod() DOES send +initialize to the class. That's why we iterate through the method list. unsigned int methodCount = 0; Method *methods = class_copyMethodList(class, &methodCount); BOOL matchingSelectorFound = NO; for (unsigned int methodIndex = 0; methodIndex < methodCount; methodIndex++) { for (int selectorIndex = 0; selectorIndex < numSelectors; ++selectorIndex) { if (method_getName(methods[methodIndex]) == selectors[selectorIndex]) { [self injectIntoDelegateClass:class]; matchingSelectorFound = YES; break; } } if (matchingSelectorFound) { break; } } free(methods); } free(classes); } [self injectIntoNSURLConnectionCancel]; [self injectIntoNSURLSessionTaskResume]; [self injectIntoNSURLConnectionAsynchronousClassMethod]; [self injectIntoNSURLConnectionSynchronousClassMethod]; [self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods]; [self injectIntoNSURLSessionAsyncUploadTaskMethods]; }); } + (void)injectIntoDelegateClass:(Class)cls { // Connections [self injectWillSendRequestIntoDelegateClass:cls]; [self injectDidReceiveDataIntoDelegateClass:cls]; [self injectDidReceiveResponseIntoDelegateClass:cls]; [self injectDidFinishLoadingIntoDelegateClass:cls]; [self injectDidFailWithErrorIntoDelegateClass:cls]; // Sessions [self injectTaskWillPerformHTTPRedirectionIntoDelegateClass:cls]; [self injectTaskDidReceiveDataIntoDelegateClass:cls]; [self injectTaskDidReceiveResponseIntoDelegateClass:cls]; [self injectTaskDidCompleteWithErrorIntoDelegateClass:cls]; [self injectRespondsToSelectorIntoDelegateClass:cls]; // Data tasks [self injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:cls]; // Download tasks [self injectDownloadTaskDidWriteDataIntoDelegateClass:cls]; [self injectDownloadTaskDidFinishDownloadingIntoDelegateClass:cls]; } + (void)injectIntoNSURLConnectionCancel { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [NSURLConnection class]; SEL selector = @selector(cancel); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Method originalCancel = class_getInstanceMethod(class, selector); 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); }); } + (void)injectIntoNSURLSessionTaskResume { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // In iOS 7 resume lives in __NSCFLocalSessionTask // In iOS 8 resume lives in NSURLSessionTask // In iOS 9 resume lives in __NSCFURLSessionTask Class class = Nil; if (![NSProcessInfo.processInfo respondsToSelector:@selector(operatingSystemVersion)]) { class = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]); } else if ([NSProcessInfo.processInfo operatingSystemVersion].majorVersion < 9) { class = [NSURLSessionTask class]; } else { class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]); } SEL selector = @selector(resume); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Method originalResume = class_getInstanceMethod(class, selector); void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) { // iOS's internal HTTP parser finalization code is mysteriously not thread safe, // invoke it asynchronously has a chance to cause a `double free` crash. // This line below will ask for HTTPBody synchronously, make the HTTPParser parse the request and cache them in advance, // After that the HTTPParser will be finalized, // make sure other threads inspecting the request won't trigger a race to finalize the parser. [slf.currentRequest HTTPBody]; [[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); }); } + (void)injectIntoNSURLConnectionAsynchronousClassMethod { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = objc_getMetaClass(class_getName([NSURLConnection class])); SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:); SEL swizzledSelector = [FLEXUtility 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) { if ([FLEXNetworkObserver isEnabled]) { NSString *requestID = [self nextRequestID]; [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil]; NSString *mechanism = [self mechanismFromClassMethod: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); } else { ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completion); } }; [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector]; }); } + (void)injectIntoNSURLConnectionSynchronousClassMethod { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = objc_getMetaClass(class_getName([NSURLConnection class])); SEL selector = @selector(sendSynchronousRequest:returningResponse:error:); SEL swizzledSelector = [FLEXUtility 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 mechanismFromClassMethod: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 { data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, response, error); } return data; }; [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector]; }); } + (void)injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ 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(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 = [FLEXUtility swizzledSelectorForSelector:selector]; if ([FLEXUtility 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]; } NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) { NSURLSessionTask *task = nil; // If completion block was not provided sender expect to receive delegated methods or does not // interested in callback at all. In this case we should just call original method implementation // with nil completion block. if ([FLEXNetworkObserver isEnabled] && completion) { NSString *requestID = [self nextRequestID]; NSString *mechanism = [self mechanismFromClassMethod: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; }; [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector]; } }); } + (void)injectIntoNSURLSessionAsyncUploadTaskMethods { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ 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 = [FLEXUtility swizzledSelectorForSelector:selector]; if ([FLEXUtility 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]; } NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) { NSURLSessionUploadTask *task = nil; if ([FLEXNetworkObserver isEnabled] && completion) { NSString *requestID = [self nextRequestID]; NSString *mechanism = [self mechanismFromClassMethod: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; }; [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:class withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector]; } }); } + (NSString *)mechanismFromClassMethod:(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(fileURLOrData, response, error); } }; return completionWrapper; } + (void)injectWillSendRequestIntoDelegateClass:(Class)cls { SEL selector = @selector(connection:willSendRequest:redirectResponse:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLConnectionDataDelegate); if (!protocol) { protocol = @protocol(NSURLConnectionDelegate); } struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef NSURLRequest *(^NSURLConnectionWillSendRequestBlock)(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response); NSURLConnectionWillSendRequestBlock undefinedBlock = ^NSURLRequest *(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) { [[FLEXNetworkObserver sharedObserver] connection:connection willSendRequest:request redirectResponse:response delegate:slf]; return request; }; NSURLConnectionWillSendRequestBlock implementationBlock = ^NSURLRequest *(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) { __block NSURLRequest *returnValue = nil; [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ undefinedBlock(slf, connection, request, response); } originalImplementationBlock:^{ returnValue = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, connection, request, response); }]; return returnValue; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDidReceiveResponseIntoDelegateClass:(Class)cls { SEL selector = @selector(connection:didReceiveResponse:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLConnectionDataDelegate); if (!protocol) { protocol = @protocol(NSURLConnectionDelegate); } struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLConnectionDidReceiveResponseBlock)(id slf, NSURLConnection *connection, NSURLResponse *response); NSURLConnectionDidReceiveResponseBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSURLResponse *response) { [[FLEXNetworkObserver sharedObserver] connection:connection didReceiveResponse:response delegate:slf]; }; NSURLConnectionDidReceiveResponseBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSURLResponse *response) { [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ undefinedBlock(slf, connection, response); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, response); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDidReceiveDataIntoDelegateClass:(Class)cls { SEL selector = @selector(connection:didReceiveData:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLConnectionDataDelegate); if (!protocol) { protocol = @protocol(NSURLConnectionDelegate); } struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLConnectionDidReceiveDataBlock)(id slf, NSURLConnection *connection, NSData *data); NSURLConnectionDidReceiveDataBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSData *data) { [[FLEXNetworkObserver sharedObserver] connection:connection didReceiveData:data delegate:slf]; }; NSURLConnectionDidReceiveDataBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSData *data) { [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ undefinedBlock(slf, connection, data); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, data); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls { SEL selector = @selector(connectionDidFinishLoading:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLConnectionDataDelegate); if (!protocol) { protocol = @protocol(NSURLConnectionDelegate); } struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLConnectionDidFinishLoadingBlock)(id slf, NSURLConnection *connection); NSURLConnectionDidFinishLoadingBlock undefinedBlock = ^(id slf, NSURLConnection *connection) { [[FLEXNetworkObserver sharedObserver] connectionDidFinishLoading:connection delegate:slf]; }; NSURLConnectionDidFinishLoadingBlock implementationBlock = ^(id slf, NSURLConnection *connection) { [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ undefinedBlock(slf, connection); } originalImplementationBlock:^{ ((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSelector, connection); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls { SEL selector = @selector(connection:didFailWithError:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLConnectionDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLConnectionDidFailWithErrorBlock)(id slf, NSURLConnection *connection, NSError *error); NSURLConnectionDidFailWithErrorBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSError *error) { [[FLEXNetworkObserver sharedObserver] connection:connection didFailWithError:error delegate:slf]; }; NSURLConnectionDidFailWithErrorBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSError *error) { [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ undefinedBlock(slf, connection, error); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, error); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectTaskWillPerformHTTPRedirectionIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionTaskDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionWillPerformHTTPRedirectionBlock)(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)); NSURLSessionWillPerformHTTPRedirectionBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) { [[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf]; completionHandler(newRequest); }; NSURLSessionWillPerformHTTPRedirectionBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ [[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf]; } originalImplementationBlock:^{ ((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectTaskDidReceiveDataIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:dataTask:didReceiveData:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionDataDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionDidReceiveDataBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data); NSURLSessionDidReceiveDataBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) { [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveData:data delegate:slf]; }; NSURLSessionDidReceiveDataBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ undefinedBlock(slf, session, dataTask, data); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, dataTask, data); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:dataTask:didBecomeDownloadTask:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionDataDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionDidBecomeDownloadTaskBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask); NSURLSessionDidBecomeDownloadTaskBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask) { [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask delegate:slf]; }; NSURLSessionDidBecomeDownloadTaskBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ undefinedBlock(slf, session, dataTask, downloadTask); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, dataTask, downloadTask); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectTaskDidReceiveResponseIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:dataTask:didReceiveResponse:completionHandler:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionDataDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionDidReceiveResponseBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)); NSURLSessionDidReceiveResponseBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) { [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf]; completionHandler(NSURLSessionResponseAllow); }; NSURLSessionDidReceiveResponseBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf]; } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectTaskDidCompleteWithErrorIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:task:didCompleteWithError:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionTaskDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionTaskDidCompleteWithErrorBlock)(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error); NSURLSessionTaskDidCompleteWithErrorBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error) { [[FLEXNetworkObserver sharedObserver] URLSession:session task:task didCompleteWithError:error delegate:slf]; }; NSURLSessionTaskDidCompleteWithErrorBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ undefinedBlock(slf, session, task, error); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, task, error); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } // Used for overriding AFNetworking behavior + (void)injectRespondsToSelectorIntoDelegateClass:(Class)cls { SEL selector = @selector(respondsToSelector:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; //Protocol *protocol = @protocol(NSURLSessionTaskDelegate); Method method = class_getInstanceMethod(cls, selector); struct objc_method_description methodDescription = *method_getDescription(method); BOOL (^undefinedBlock)(id , SEL) = ^(id slf, SEL sel) { return YES; }; BOOL (^implementationBlock)(id , SEL) = ^(id slf, SEL sel) { if (sel == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) { return undefinedBlock(slf, sel); } return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(slf, swizzledSelector, sel); }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDownloadTaskDidFinishDownloadingIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:downloadTask:didFinishDownloadingToURL:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionDownloadDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionDownloadTaskDidFinishDownloadingBlock)(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location); NSURLSessionDownloadTaskDidFinishDownloadingBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location) { NSData *data = [NSData dataWithContentsOfFile:location.relativePath]; [[FLEXNetworkObserver sharedObserver] URLSession:session task:task didFinishDownloadingToURL:location data:data delegate:slf]; }; NSURLSessionDownloadTaskDidFinishDownloadingBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ undefinedBlock(slf, session, task, location); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, task, location); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } + (void)injectDownloadTaskDidWriteDataIntoDelegateClass:(Class)cls { SEL selector = @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:); SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; Protocol *protocol = @protocol(NSURLSessionDownloadDelegate); struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); typedef void (^NSURLSessionDownloadTaskDidWriteDataBlock)(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); NSURLSessionDownloadTaskDidWriteDataBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { [[FLEXNetworkObserver sharedObserver] URLSession:session downloadTask:task didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite delegate:slf]; }; NSURLSessionDownloadTaskDidWriteDataBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ undefinedBlock(slf, session, task, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } originalImplementationBlock:^{ ((void(*)(id, SEL, id, id, int64_t, int64_t, int64_t))objc_msgSend)(slf, swizzledSelector, session, task, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); }]; }; [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; } static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey"; + (NSString *)requestIDForConnectionOrTask:(id)connectionOrTask { NSString *requestID = objc_getAssociatedObject(connectionOrTask, kFLEXRequestIDKey); if (!requestID) { requestID = [self nextRequestID]; [self setRequestID:requestID forConnectionOrTask:connectionOrTask]; } return requestID; } + (void)setRequestID:(NSString *)requestID forConnectionOrTask:(id)connectionOrTask { objc_setAssociatedObject(connectionOrTask, kFLEXRequestIDKey, requestID, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - Initialization - (id)init { self = [super init]; if (self) { self.requestStatesForRequestIDs = [NSMutableDictionary new]; self.queue = dispatch_queue_create("com.flex.FLEXNetworkObserver", DISPATCH_QUEUE_SERIAL); } return self; } #pragma mark - Private Methods - (void)performBlock:(dispatch_block_t)block { if ([[self class] isEnabled]) { dispatch_async(_queue, block); } } - (FLEXInternalRequestState *)requestStateForRequestID:(NSString *)requestID { FLEXInternalRequestState *requestState = self.requestStatesForRequestIDs[requestID]; if (!requestState) { requestState = [FLEXInternalRequestState new]; [self.requestStatesForRequestIDs setObject:requestState forKey:requestID]; } return requestState; } - (void)removeRequestStateForRequestID:(NSString *)requestID { [self.requestStatesForRequestIDs removeObjectForKey:requestID]; } @end @implementation FLEXNetworkObserver (NSURLConnectionHelpers) - (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; requestState.request = request; [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:response]; NSString *mechanism = [NSString stringWithFormat:@"NSURLConnection (delegate: %@)", [delegate class]]; [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID]; }]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; NSMutableData *dataAccumulator = nil; if (response.expectedContentLength < 0) { dataAccumulator = [NSMutableData new]; } else if (response.expectedContentLength < 52428800) { dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength]; } requestState.dataAccumulator = dataAccumulator; [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; }]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id)delegate { // Just to be safe since we're doing this async data = [data copy]; [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; [requestState.dataAccumulator appendData:data]; [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:data.length]; }]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:requestState.dataAccumulator]; [self removeRequestStateForRequestID:requestID]; }]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; // Cancellations can occur prior to the willSendRequest:... NSURLConnection delegate call. // These are pretty common and clutter up the logs. Only record the failure if the recorder already knows about the request through willSendRequest:... if (requestState.request) { [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error]; } [self removeRequestStateForRequestID:requestID]; }]; } - (void)connectionWillCancel:(NSURLConnection *)connection { [self performBlock:^{ // Mimic the behavior of NSURLSession which is to create an error on cancellation. NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" }; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; [self connection:connection didFailWithError:error delegate:nil]; }]; } @end @implementation FLEXNetworkObserver (NSURLSessionTaskHelpers) - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:response]; }]; } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; NSMutableData *dataAccumulator = nil; if (response.expectedContentLength < 0) { dataAccumulator = [NSMutableData new]; } else { dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength]; } requestState.dataAccumulator = dataAccumulator; NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDataTask (delegate: %@)", [delegate class]]; [[FLEXNetworkRecorder defaultRecorder] recordMechanism:requestMechanism forRequestID:requestID]; [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; }]; } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id)delegate { [self performBlock:^{ // By setting the request ID of the download task to match the data task, // it can pick up where the data task left off. NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; [[self class] setRequestID:requestID forConnectionOrTask:downloadTask]; }]; } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id)delegate { // Just to be safe since we're doing this async data = [data copy]; [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; [requestState.dataAccumulator appendData:data]; [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:data.length]; }]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; if (error) { [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error]; } else { [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:requestState.dataAccumulator]; } [self removeRequestStateForRequestID:requestID]; }]; } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id)delegate { [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; if (!requestState.dataAccumulator) { NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? (NSUInteger)totalBytesExpectedToWrite : 0; requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:unsignedBytesExpectedToWrite]; [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:downloadTask.response]; NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDownloadTask (delegate: %@)", [delegate class]]; [[FLEXNetworkRecorder defaultRecorder] recordMechanism:requestMechanism forRequestID:requestID]; } [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:bytesWritten]; }]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id)delegate { data = [data copy]; [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; [requestState.dataAccumulator appendData:data]; }]; } - (void)URLSessionTaskWillResume:(NSURLSessionTask *)task { // Since resume can be called multiple times on the same task, only treat the first resume as // the equivalent to connection:willSendRequest:... [self performBlock:^{ NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; if (!requestState.request) { requestState.request = task.currentRequest; [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:task.currentRequest redirectResponse:nil]; } }]; } @end