You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1130 lines
58 KiB
1130 lines
58 KiB
//
|
|
// 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 <objc/runtime.h>
|
|
#import <objc/message.h>
|
|
#import <dispatch/queue.h>
|
|
|
|
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 <NSURLConnectionDelegate>)delegate;
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id <NSURLConnectionDelegate>)delegate;
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id <NSURLConnectionDelegate>)delegate;
|
|
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id <NSURLConnectionDelegate>)delegate;
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id <NSURLConnectionDelegate>)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 <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
|
|
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id <NSURLSessionDelegate>)delegate;
|
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id <NSURLSessionDelegate>)delegate;
|
|
|
|
- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task;
|
|
|
|
@end
|
|
|
|
@interface FLEXNetworkObserver ()
|
|
|
|
@property (nonatomic) NSMutableDictionary<NSString *, FLEXInternalRequestState *> *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 <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response);
|
|
|
|
NSURLConnectionWillSendRequestBlock undefinedBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) {
|
|
[[FLEXNetworkObserver sharedObserver] connection:connection willSendRequest:request redirectResponse:response delegate:slf];
|
|
return request;
|
|
};
|
|
|
|
NSURLConnectionWillSendRequestBlock implementationBlock = ^NSURLRequest *(id <NSURLConnectionDelegate> 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 <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response);
|
|
|
|
NSURLConnectionDidReceiveResponseBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response) {
|
|
[[FLEXNetworkObserver sharedObserver] connection:connection didReceiveResponse:response delegate:slf];
|
|
};
|
|
|
|
NSURLConnectionDidReceiveResponseBlock implementationBlock = ^(id <NSURLConnectionDelegate> 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 <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data);
|
|
|
|
NSURLConnectionDidReceiveDataBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data) {
|
|
[[FLEXNetworkObserver sharedObserver] connection:connection didReceiveData:data delegate:slf];
|
|
};
|
|
|
|
NSURLConnectionDidReceiveDataBlock implementationBlock = ^(id <NSURLConnectionDelegate> 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 <NSURLConnectionDelegate> slf, NSURLConnection *connection);
|
|
|
|
NSURLConnectionDidFinishLoadingBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection) {
|
|
[[FLEXNetworkObserver sharedObserver] connectionDidFinishLoading:connection delegate:slf];
|
|
};
|
|
|
|
NSURLConnectionDidFinishLoadingBlock implementationBlock = ^(id <NSURLConnectionDelegate> 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 <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error);
|
|
|
|
NSURLConnectionDidFailWithErrorBlock undefinedBlock = ^(id <NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error) {
|
|
[[FLEXNetworkObserver sharedObserver] connection:connection didFailWithError:error delegate:slf];
|
|
};
|
|
|
|
NSURLConnectionDidFailWithErrorBlock implementationBlock = ^(id <NSURLConnectionDelegate> 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 <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *));
|
|
|
|
NSURLSessionWillPerformHTTPRedirectionBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate> 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 <NSURLSessionDataDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
|
|
|
|
NSURLSessionDidReceiveDataBlock undefinedBlock = ^(id <NSURLSessionDataDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) {
|
|
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveData:data delegate:slf];
|
|
};
|
|
|
|
NSURLSessionDidReceiveDataBlock implementationBlock = ^(id <NSURLSessionDataDelegate> 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 <NSURLSessionDataDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
|
|
|
|
NSURLSessionDidBecomeDownloadTaskBlock undefinedBlock = ^(id <NSURLSessionDataDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask) {
|
|
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask delegate:slf];
|
|
};
|
|
|
|
NSURLSessionDidBecomeDownloadTaskBlock implementationBlock = ^(id <NSURLSessionDataDelegate> 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 <NSURLSessionDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition));
|
|
|
|
NSURLSessionDidReceiveResponseBlock undefinedBlock = ^(id <NSURLSessionDelegate> 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 <NSURLSessionDelegate> 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 <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSError *error);
|
|
|
|
NSURLSessionTaskDidCompleteWithErrorBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSError *error) {
|
|
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task didCompleteWithError:error delegate:slf];
|
|
};
|
|
|
|
NSURLSessionTaskDidCompleteWithErrorBlock implementationBlock = ^(id <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate>, SEL) = ^(id slf, SEL sel) {
|
|
return YES;
|
|
};
|
|
|
|
BOOL (^implementationBlock)(id <NSURLSessionTaskDelegate>, SEL) = ^(id <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location);
|
|
|
|
NSURLSessionDownloadTaskDidFinishDownloadingBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
|
|
|
|
NSURLSessionDownloadTaskDidWriteDataBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> 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 <NSURLSessionTaskDelegate> 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<NSURLConnectionDelegate>)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<NSURLConnectionDelegate>)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<NSURLConnectionDelegate>)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<NSURLConnectionDelegate>)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<NSURLConnectionDelegate>)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<NSString *, id> *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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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<NSURLSessionDelegate>)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
|