mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-10-30 12:23:58 -04:00
1998 lines
87 KiB
Objective-C
1998 lines
87 KiB
Objective-C
//
|
|
// 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.
|
|
//
|
|
// Heavily modified and added to by Tanner Bennett and various other contributors.
|
|
// git blame details these modifications.
|
|
//
|
|
|
|
#import "FLEXNetworkObserver.h"
|
|
#import "FLEXNetworkRecorder.h"
|
|
#import "FLEXUtility.h"
|
|
#import "NSUserDefaults+FLEX.h"
|
|
#import "NSObject+FLEX_Reflection.h"
|
|
#import "FLEXMethod.h"
|
|
#import "Firestore.h"
|
|
|
|
#import <objc/runtime.h>
|
|
#import <objc/message.h>
|
|
#import <dispatch/queue.h>
|
|
#include <dlfcn.h>
|
|
|
|
NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification";
|
|
|
|
typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error);
|
|
typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(NSURLSession *, id, NSURLSessionAsyncCompletion);
|
|
|
|
@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;
|
|
|
|
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
|
|
sendMessagage:(NSURLSessionWebSocketMessage *)message API_AVAILABLE(ios(13.0));
|
|
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
|
|
error:(NSError *)error API_AVAILABLE(ios(13.0));
|
|
|
|
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
|
|
receiveMessagage:(NSURLSessionWebSocketMessage *)message
|
|
error:(NSError *)error API_AVAILABLE(ios(13.0));
|
|
|
|
@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.flex_networkObserverEnabled = enabled;
|
|
|
|
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 setNetworkMonitorHooks];
|
|
}
|
|
|
|
if (previouslyEnabled != enabled) {
|
|
[NSNotificationCenter.defaultCenter postNotificationName:kFLEXNetworkObserverEnabledStateChangedNotification object:self];
|
|
}
|
|
}
|
|
|
|
+ (BOOL)isEnabled {
|
|
return NSUserDefaults.standardUserDefaults.flex_networkObserverEnabled;
|
|
}
|
|
|
|
+ (void)load {
|
|
// We don't want to do the swizzling from +load because not all the
|
|
// delegate classes we want to hook may be loaded at this point.
|
|
// However, Firebase classes will definitely be loaded by now,
|
|
// so we can definitely hook those sooner if need be.
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if ([self isEnabled]) {
|
|
[self setNetworkMonitorHooks];
|
|
}
|
|
});
|
|
}
|
|
|
|
#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 - Hooking
|
|
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
|
|
static void (*_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
|
|
static void _logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
|
|
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, BOOL, void (^)(NSError *));
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, NSArray *, void (^)(NSError *));
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, void (^)(NSError *));
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, void (^)(NSError *));
|
|
|
|
static void _logos_register_hook(Class _class, SEL _cmd, IMP _new, IMP *_old) {
|
|
unsigned int _count, _i;
|
|
Class _searchedClass = _class;
|
|
Method *_methods;
|
|
while (_searchedClass) {
|
|
_methods = class_copyMethodList(_searchedClass, &_count);
|
|
for (_i = 0; _i < _count; _i++) {
|
|
if (method_getName(_methods[_i]) == _cmd) {
|
|
if (_class == _searchedClass) {
|
|
*_old = method_getImplementation(_methods[_i]);
|
|
*_old = class_replaceMethod(_class, _cmd, _new, method_getTypeEncoding(_methods[_i]));
|
|
} else {
|
|
class_addMethod(_class, _cmd, _new, method_getTypeEncoding(_methods[_i]));
|
|
}
|
|
free(_methods);
|
|
return;
|
|
}
|
|
}
|
|
free(_methods);
|
|
_searchedClass = class_getSuperclass(_searchedClass);
|
|
}
|
|
}
|
|
|
|
static Class _logos_superclass$_ungrouped$FIRDocumentReference;
|
|
static void (*_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST, SEL, FIRDocumentSnapshotBlock);
|
|
static Class _logos_superclass$_ungrouped$FIRQuery;
|
|
static void (*_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST, SEL, FIRQuerySnapshotBlock);
|
|
static Class _logos_superclass$_ungrouped$FIRCollectionReference;
|
|
static FIRDocumentReference * (*_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$)(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRCollectionReference * _LOGOS_SELF_CONST, SEL, NSDictionary *, void (^)(NSError *error));
|
|
|
|
#pragma mark Firebase, Reading Data
|
|
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST self, SEL _cmd, FIRDocumentSnapshotBlock completion) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDocumentWillFetch:self withTransactionID:requestID];
|
|
// Hook callback
|
|
FIRDocumentSnapshotBlock orig = completion;
|
|
completion = ^(FIRDocumentSnapshot *document, NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDocumentDidFetch:document error:error transactionID:requestID];
|
|
orig(document, error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$ ? _logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(getDocumentWithCompletion:)))(self, _cmd, completion);
|
|
}
|
|
|
|
static void _logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRQuery * _LOGOS_SELF_CONST self, SEL _cmd, FIRQuerySnapshotBlock completion) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRQueryWillFetch:self withTransactionID:requestID];
|
|
// Hook callback
|
|
FIRQuerySnapshotBlock orig = completion;
|
|
completion = ^(FIRQuerySnapshot *query, NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRQueryDidFetch:query error:error transactionID:requestID];
|
|
orig(query, error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$ ? _logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRQuery, @selector(getDocumentsWithCompletion:)))(self, _cmd, completion);
|
|
}
|
|
|
|
#pragma mark Firebase, Writing Data
|
|
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$setData$merge$completion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
|
|
SEL __unused _cmd, NSDictionary<NSString *, id> * documentData, BOOL merge, void (^completion)(NSError *)) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder
|
|
recordFIRWillSetData:self
|
|
data:documentData
|
|
merge:@(merge)
|
|
mergeFields:nil
|
|
transactionID:requestID
|
|
];
|
|
|
|
// Hook callback
|
|
void (^orig)(NSError *) = completion;
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDidSetData:error transactionID:requestID];
|
|
orig(error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(setData:merge:completion:)))(self, _cmd, documentData, merge, completion);
|
|
}
|
|
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
|
|
SEL __unused _cmd, NSDictionary<NSString *, id> * documentData,
|
|
NSArray * mergeFields, void (^completion)(NSError *)) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder
|
|
recordFIRWillSetData:self
|
|
data:documentData
|
|
merge:nil
|
|
mergeFields:mergeFields
|
|
transactionID:requestID
|
|
];
|
|
|
|
// Hook callback
|
|
void (^orig)(NSError *) = completion;
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDidSetData:error transactionID:requestID];
|
|
orig(error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(setData:mergeFields:completion:)))(self, _cmd, documentData, mergeFields, completion);
|
|
}
|
|
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$updateData$completion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
|
|
SEL __unused _cmd, NSDictionary<id, id> * fields, void (^completion)(NSError *)) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRWillUpdateData:self fields:fields transactionID:requestID];
|
|
// Hook callback
|
|
void (^orig)(NSError *) = completion;
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDidUpdateData:error transactionID:requestID];
|
|
orig(error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$ ? _logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(updateData:completion:)))(self, _cmd, fields, completion);
|
|
}
|
|
|
|
static void _logos_method$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRDocumentReference * _LOGOS_SELF_CONST __unused self,
|
|
SEL __unused _cmd, void (^completion)(NSError *)) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRWillDeleteDocument:self transactionID:requestID];
|
|
// Hook callback
|
|
void (^orig)(NSError *) = completion;
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDidDeleteDocument:error transactionID:requestID];
|
|
orig(error);
|
|
};
|
|
|
|
// Forward invocation
|
|
(_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$ ? _logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$ : (__typeof__(_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRDocumentReference, @selector(deleteDocumentWithCompletion:)))(self, _cmd, completion);
|
|
}
|
|
|
|
static FIRDocumentReference * _logos_method$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$(
|
|
_LOGOS_SELF_TYPE_NORMAL FIRCollectionReference * _LOGOS_SELF_CONST __unused self,
|
|
SEL __unused _cmd, NSDictionary<NSString *, id> * data, void (^completion)(NSError *error)) {
|
|
|
|
// Generate transaction ID
|
|
NSString *requestID = [FLEXNetworkObserver nextRequestID];
|
|
|
|
// Hook callback
|
|
void (^orig)(NSError *) = completion;
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRDidAddDocument:error transactionID:requestID];
|
|
orig(error);
|
|
};
|
|
|
|
// Forward invocation
|
|
FIRDocumentReference *ret = (_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$ ? _logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$ : (__typeof__(_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$))class_getMethodImplementation(_logos_superclass$_ungrouped$FIRCollectionReference, @selector(addDocumentWithData:completion:)))(self, _cmd, data, completion);
|
|
|
|
// Record transaction start
|
|
[FLEXNetworkRecorder.defaultRecorder recordFIRWillAddDocument:self document:ret transactionID:requestID];
|
|
|
|
// Return
|
|
return ret;
|
|
}
|
|
|
|
+ (void)setNetworkMonitorHooks {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
[self hookFirebaseThings];
|
|
[self injectIntoAllNSURLThings];
|
|
});
|
|
}
|
|
|
|
+ (void)hookFirebaseThings {
|
|
Class _logos_class$_ungrouped$FIRDocumentReference = objc_getClass("FIRDocumentReference");
|
|
_logos_superclass$_ungrouped$FIRDocumentReference = class_getSuperclass(_logos_class$_ungrouped$FIRDocumentReference);
|
|
Class _logos_class$_ungrouped$FIRQuery = objc_getClass("FIRQuery");
|
|
_logos_superclass$_ungrouped$FIRQuery = class_getSuperclass(_logos_class$_ungrouped$FIRQuery);
|
|
Class _logos_class$_ungrouped$FIRCollectionReference = objc_getClass("FIRCollectionReference");
|
|
_logos_superclass$_ungrouped$FIRCollectionReference = class_getSuperclass(_logos_class$_ungrouped$FIRCollectionReference);
|
|
|
|
// Reading //
|
|
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRDocumentReference,
|
|
@selector(getDocumentWithCompletion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$getDocumentWithCompletion$
|
|
);
|
|
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRQuery,
|
|
@selector(getDocumentsWithCompletion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRQuery$getDocumentsWithCompletion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRQuery$getDocumentsWithCompletion$
|
|
);
|
|
|
|
// Writing //
|
|
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRDocumentReference,
|
|
@selector(setData:merge:completion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRDocumentReference$setData$merge$completion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$setData$merge$completion$
|
|
);
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRDocumentReference,
|
|
@selector(setData:mergeFields:completion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$setData$mergeFields$completion$
|
|
);
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRDocumentReference,
|
|
@selector(updateData:completion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRDocumentReference$updateData$completion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$updateData$completion$
|
|
);
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRDocumentReference,
|
|
@selector(deleteDocumentWithCompletion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRDocumentReference$deleteDocumentWithCompletion$
|
|
);
|
|
_logos_register_hook(
|
|
_logos_class$_ungrouped$FIRCollectionReference,
|
|
@selector(addDocumentWithData:completion:),
|
|
(IMP)&_logos_method$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$,
|
|
(IMP *)&_logos_orig$_ungrouped$FIRCollectionReference$addDocumentWithData$completion$
|
|
);
|
|
}
|
|
|
|
+ (void)injectIntoAllNSURLThings {
|
|
// 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 C API rather than NSObject methods to avoid sending messages
|
|
// to classes we're not interested in swizzling, which could result
|
|
// in us calling +initialize on potentially uninitialized 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];
|
|
|
|
if (@available(iOS 13.0, *)) {
|
|
Class websocketTask = NSClassFromString(@"__NSURLSessionWebSocketTask");
|
|
[self injectWebsocketSendMessage:websocketTask];
|
|
[self injectWebsocketReceiveMessage:websocketTask];
|
|
websocketTask = [NSURLSessionWebSocketTask class];
|
|
[self injectWebsocketSendMessage:websocketTask];
|
|
[self injectWebsocketReceiveMessage:websocketTask];
|
|
}
|
|
});
|
|
}
|
|
|
|
+ (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
|
|
// In iOS 14 resume lives in NSURLSessionTask
|
|
Class baseResumeClass = Nil;
|
|
if (![NSProcessInfo.processInfo respondsToSelector:@selector(operatingSystemVersion)]) {
|
|
// iOS ... 7
|
|
baseResumeClass = NSClassFromString(@"__NSCFLocalSessionTask");
|
|
} else {
|
|
NSInteger majorVersion = NSProcessInfo.processInfo.operatingSystemVersion.majorVersion;
|
|
if (majorVersion < 9 || majorVersion >= 14) {
|
|
// iOS 8 or iOS 14+
|
|
baseResumeClass = [NSURLSessionTask class];
|
|
} else {
|
|
// iOS 9 ... 13
|
|
baseResumeClass = NSClassFromString(@"__NSCFURLSessionTask");
|
|
}
|
|
}
|
|
|
|
// Hook the base implementation of -resume
|
|
IMP originalResume = [baseResumeClass instanceMethodForSelector:@selector(resume)];
|
|
[self swizzleResumeSelector:@selector(resume) forClass:baseResumeClass];
|
|
|
|
// *Sigh*
|
|
//
|
|
// So, multiple versions of AFNetworking 2.5.X swizzle -resume in various and
|
|
// short-sighted ways. If you look through the version history from 2.5.0 upwards,
|
|
// you'll see a variety of techniques were tried, including taking a private
|
|
// subclass of NSURLSessionTask and calling class_addMethod with `originalResume`
|
|
// below, so that a duplicate implementation of -resume exists in that class.
|
|
//
|
|
// This technique in particular is troublesome, because the implementation in
|
|
// `baseResumeClass` is never called at all, which means our swizzle is never invoked.
|
|
//
|
|
// The only solution is a brute-force one: we must loop over the class tree
|
|
// below `baseResumeClass` and check for all classes that implement `af_resume`.
|
|
// if the IMP corresponding to that method is equal to `originalResume` then we
|
|
// swizzle that in addition to swizzling `resume` on `baseResumeClass` above.
|
|
//
|
|
// However, we only go to the trouble at all if NSSelectorFromString
|
|
// can even find an `"af_resume"` selector in the first place.
|
|
SEL sel_af_resume = NSSelectorFromString(@"af_resume");
|
|
if (sel_af_resume) {
|
|
NSMutableArray<Class> *classTree = FLEXGetAllSubclasses(baseResumeClass, NO).mutableCopy;
|
|
for (NSInteger i = 0; i < classTree.count; i++) {
|
|
[classTree addObjectsFromArray:FLEXGetAllSubclasses(classTree[i], NO)];
|
|
}
|
|
|
|
for (Class current in classTree) {
|
|
IMP af_resume = [current instanceMethodForSelector:sel_af_resume];
|
|
if (af_resume == originalResume) {
|
|
[self swizzleResumeSelector:sel_af_resume forClass:current];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
+ (void)swizzleResumeSelector:(SEL)selector forClass:(Class)class {
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
Method originalResume = class_getInstanceMethod(class, selector);
|
|
IMP implementation = imp_implementationWithBlock(^(NSURLSessionTask *slf) {
|
|
|
|
// iOS's internal HTTP parser finalization code is mysteriously not thread safe,
|
|
// invoking 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
|
|
);
|
|
});
|
|
|
|
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 (^AsyncCompletion)(
|
|
NSURLResponse *response, NSData *data, NSError *error
|
|
);
|
|
typedef void (^SendAsyncRequestBlock)(
|
|
Class, NSURLRequest *, NSOperationQueue *, AsyncCompletion
|
|
);
|
|
SendAsyncRequestBlock swizzleBlock = ^(Class slf,
|
|
NSURLRequest *request,
|
|
NSOperationQueue *queue,
|
|
AsyncCompletion 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];
|
|
|
|
AsyncCompletion wrapper = ^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
[FLEXNetworkRecorder.defaultRecorder
|
|
recordResponseReceivedWithRequestID:requestID
|
|
response:response
|
|
];
|
|
[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(response, data, error);
|
|
}
|
|
};
|
|
((void(*)(id, SEL, id, id, id))objc_msgSend)(
|
|
slf, swizzledSelector, request, queue, wrapper
|
|
);
|
|
} else {
|
|
((void(*)(id, SEL, id, id, id))objc_msgSend)(
|
|
slf, swizzledSelector, request, queue, completion
|
|
);
|
|
}
|
|
};
|
|
|
|
[FLEXUtility replaceImplementationOfKnownSelector:selector
|
|
onClass:class withBlock:swizzleBlock 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];
|
|
|
|
typedef NSData * (^AsyncCompletion)(Class, NSURLRequest *, NSURLResponse **, NSError **);
|
|
AsyncCompletion swizzleBlock = ^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:swizzleBlock 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];
|
|
}
|
|
|
|
typedef NSURLSessionTask * (^NSURLSessionNewTaskMethod)(
|
|
NSURLSession *, id, NSURLSessionAsyncCompletion
|
|
);
|
|
NSURLSessionNewTaskMethod swizzleBlock = ^NSURLSessionTask *(NSURLSession *slf,
|
|
id argument,
|
|
NSURLSessionAsyncCompletion completion) {
|
|
NSURLSessionTask *task = nil;
|
|
// Check if network observing is on and a callback was provided
|
|
if (FLEXNetworkObserver.isEnabled && completion) {
|
|
NSString *requestID = [self nextRequestID];
|
|
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
|
|
// "Hook" the completion block
|
|
NSURLSessionAsyncCompletion completionWrapper = [self
|
|
asyncCompletionWrapperForRequestID:requestID
|
|
mechanism:mechanism
|
|
completion:completion
|
|
];
|
|
|
|
// Call the original method
|
|
task = ((id(*)(id, SEL, id, id))objc_msgSend)(
|
|
slf, swizzledSelector, argument, completionWrapper
|
|
);
|
|
[self setRequestID:requestID forConnectionOrTask:task];
|
|
} else {
|
|
// Network observer disabled or no callback provided,
|
|
// just pass through to the original method
|
|
task = ((id(*)(id, SEL, id, id))objc_msgSend)(
|
|
slf, swizzledSelector, argument, completion
|
|
);
|
|
}
|
|
return task;
|
|
};
|
|
|
|
// Actually swizzle
|
|
[FLEXUtility replaceImplementationOfKnownSelector:selector
|
|
onClass:class withBlock:swizzleBlock 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.
|
|
typedef NSURLSessionUploadTask *(^UploadTaskMethod)(
|
|
NSURLSession *, NSURLRequest *, id, NSURLSessionAsyncCompletion
|
|
);
|
|
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];
|
|
}
|
|
|
|
|
|
UploadTaskMethod swizzleBlock = ^NSURLSessionUploadTask *(NSURLSession * 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:swizzleBlock 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);
|
|
protocol = protocol ?: @protocol(NSURLConnectionDelegate);
|
|
struct objc_method_description methodDescription = protocol_getMethodDescription(
|
|
protocol, selector, NO, YES
|
|
);
|
|
|
|
typedef NSURLRequest *(^WillSendRequestBlock)(
|
|
id<NSURLConnectionDelegate> slf, NSURLConnection *connection,
|
|
NSURLRequest *request, NSURLResponse *response
|
|
);
|
|
|
|
WillSendRequestBlock undefinedBlock = ^NSURLRequest *(id slf,
|
|
NSURLConnection *connection,
|
|
NSURLRequest *request,
|
|
NSURLResponse *response) {
|
|
[FLEXNetworkObserver.sharedObserver
|
|
connection:connection
|
|
willSendRequest:request
|
|
redirectResponse:response
|
|
delegate:slf
|
|
];
|
|
return request;
|
|
};
|
|
|
|
WillSendRequestBlock 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);
|
|
protocol = protocol ?: @protocol(NSURLConnectionDelegate);
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
protocol, selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidReceiveResponseBlock)(
|
|
id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSURLResponse *response
|
|
);
|
|
|
|
DidReceiveResponseBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
|
|
NSURLConnection *connection,
|
|
NSURLResponse *response) {
|
|
[FLEXNetworkObserver.sharedObserver connection:connection
|
|
didReceiveResponse:response delegate:slf
|
|
];
|
|
};
|
|
|
|
DidReceiveResponseBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectDidReceiveDataIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(connection:didReceiveData:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
|
protocol = protocol ?: @protocol(NSURLConnectionDelegate);
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
protocol, selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidReceiveDataBlock)(
|
|
id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSData *data
|
|
);
|
|
|
|
DidReceiveDataBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
|
|
NSURLConnection *connection,
|
|
NSData *data) {
|
|
[FLEXNetworkObserver.sharedObserver connection:connection
|
|
didReceiveData:data delegate:slf
|
|
];
|
|
};
|
|
|
|
DidReceiveDataBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(connectionDidFinishLoading:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
Protocol *protocol = @protocol(NSURLConnectionDataDelegate);
|
|
protocol = protocol ?: @protocol(NSURLConnectionDelegate);
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
protocol, selector, NO, YES
|
|
);
|
|
|
|
typedef void (^FinishLoadingBlock)(id<NSURLConnectionDelegate> slf, NSURLConnection *connection);
|
|
|
|
FinishLoadingBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf, NSURLConnection *connection) {
|
|
[FLEXNetworkObserver.sharedObserver connectionDidFinishLoading:connection delegate:slf];
|
|
};
|
|
|
|
FinishLoadingBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(connection:didFailWithError:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLConnectionDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidFailWithErrorBlock)(
|
|
id<NSURLConnectionDelegate> slf, NSURLConnection *connection, NSError *error
|
|
);
|
|
|
|
DidFailWithErrorBlock undefinedBlock = ^(id<NSURLConnectionDelegate> slf,
|
|
NSURLConnection *connection,
|
|
NSError *error) {
|
|
[FLEXNetworkObserver.sharedObserver connection:connection
|
|
didFailWithError:error delegate:slf
|
|
];
|
|
};
|
|
|
|
DidFailWithErrorBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectTaskWillPerformHTTPRedirectionIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionTaskDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^HTTPRedirectionBlock)(id<NSURLSessionTaskDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionTask *task,
|
|
NSHTTPURLResponse *response,
|
|
NSURLRequest *newRequest,
|
|
void(^completionHandler)(NSURLRequest *));
|
|
|
|
HTTPRedirectionBlock 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);
|
|
};
|
|
|
|
HTTPRedirectionBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectTaskDidReceiveDataIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:dataTask:didReceiveData:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDataDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidReceiveDataBlock)(id<NSURLSessionDataDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSData *data);
|
|
DidReceiveDataBlock undefinedBlock = ^(id<NSURLSessionDataDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSData *data) {
|
|
[FLEXNetworkObserver.sharedObserver URLSession:session
|
|
dataTask:dataTask didReceiveData:data delegate:slf
|
|
];
|
|
};
|
|
|
|
DidReceiveDataBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:dataTask:didBecomeDownloadTask:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDataDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidBecomeDownloadTaskBlock)(id<NSURLSessionDataDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSURLSessionDownloadTask *downloadTask);
|
|
|
|
DidBecomeDownloadTaskBlock undefinedBlock = ^(id<NSURLSessionDataDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSURLSessionDownloadTask *downloadTask) {
|
|
[FLEXNetworkObserver.sharedObserver URLSession:session
|
|
dataTask:dataTask didBecomeDownloadTask:downloadTask delegate:slf
|
|
];
|
|
};
|
|
|
|
DidBecomeDownloadTaskBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectTaskDidReceiveResponseIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:dataTask:didReceiveResponse:completionHandler:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDataDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidReceiveResponseBlock)(id<NSURLSessionDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSURLResponse *response,
|
|
void(^completion)(NSURLSessionResponseDisposition));
|
|
|
|
DidReceiveResponseBlock undefinedBlock = ^(id<NSURLSessionDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSURLResponse *response,
|
|
void(^completion)(NSURLSessionResponseDisposition)) {
|
|
[FLEXNetworkObserver.sharedObserver
|
|
URLSession:session
|
|
dataTask:dataTask
|
|
didReceiveResponse:response
|
|
completionHandler:completion
|
|
delegate:slf
|
|
];
|
|
completion(NSURLSessionResponseAllow);
|
|
};
|
|
|
|
DidReceiveResponseBlock implementationBlock = ^(id<NSURLSessionDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDataTask *dataTask,
|
|
NSURLResponse *response,
|
|
void(^completion)(NSURLSessionResponseDisposition )) {
|
|
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
|
|
[FLEXNetworkObserver.sharedObserver
|
|
URLSession:session
|
|
dataTask:dataTask
|
|
didReceiveResponse:response
|
|
completionHandler:completion
|
|
delegate:slf
|
|
];
|
|
} originalImplementationBlock:^{
|
|
((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(
|
|
slf, swizzledSelector, session, dataTask, response, completion
|
|
);
|
|
}];
|
|
};
|
|
|
|
[FLEXUtility replaceImplementationOfSelector:selector
|
|
withSelector:swizzledSelector
|
|
forClass:cls
|
|
withMethodDescription:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
|
|
}
|
|
|
|
+ (void)injectTaskDidCompleteWithErrorIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:task:didCompleteWithError:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDataDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidCompleteWithErrorBlock)(id<NSURLSessionTaskDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionTask *task,
|
|
NSError *error);
|
|
|
|
DidCompleteWithErrorBlock undefinedBlock = ^(id<NSURLSessionTaskDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionTask *task,
|
|
NSError *error) {
|
|
[FLEXNetworkObserver.sharedObserver URLSession:session
|
|
task:task didCompleteWithError:error delegate:slf
|
|
];
|
|
};
|
|
|
|
DidCompleteWithErrorBlock 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:description
|
|
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);
|
|
|
|
typedef BOOL (^RespondsToSelectorImpl)(id self, SEL sel);
|
|
RespondsToSelectorImpl undefinedBlock = ^(id slf, SEL sel) {
|
|
return YES;
|
|
};
|
|
|
|
RespondsToSelectorImpl implementationBlock = ^(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];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDownloadDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidFinishDownloadingBlock)(id<NSURLSessionTaskDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDownloadTask *task,
|
|
NSURL *location);
|
|
|
|
DidFinishDownloadingBlock 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
|
|
];
|
|
};
|
|
|
|
DidFinishDownloadingBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectDownloadTaskDidWriteDataIntoDelegateClass:(Class)cls {
|
|
SEL selector = @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
struct objc_method_description description = protocol_getMethodDescription(
|
|
@protocol(NSURLSessionDownloadDelegate), selector, NO, YES
|
|
);
|
|
|
|
typedef void (^DidWriteDataBlock)(id<NSURLSessionTaskDelegate> slf,
|
|
NSURLSession *session,
|
|
NSURLSessionDownloadTask *task,
|
|
int64_t bytesWritten,
|
|
int64_t totalBytesWritten,
|
|
int64_t totalBytesExpectedToWrite);
|
|
|
|
DidWriteDataBlock 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
|
|
];
|
|
};
|
|
|
|
DidWriteDataBlock 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:description
|
|
implementationBlock:implementationBlock
|
|
undefinedBlock:undefinedBlock
|
|
];
|
|
}
|
|
|
|
+ (void)injectWebsocketSendMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
|
|
SEL selector = @selector(sendMessage:completionHandler:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
typedef void (^SendMessageBlock)(
|
|
NSURLSessionWebSocketTask *slf,
|
|
NSURLSessionWebSocketMessage *message,
|
|
void (^completion)(NSError *error)
|
|
);
|
|
|
|
SendMessageBlock implementationBlock = ^(
|
|
NSURLSessionWebSocketTask *slf,
|
|
NSURLSessionWebSocketMessage *message,
|
|
void (^completion)(NSError *error)
|
|
) {
|
|
[FLEXNetworkObserver.sharedObserver
|
|
websocketTask:slf sendMessagage:message
|
|
];
|
|
completion = ^(NSError *error) {
|
|
[FLEXNetworkObserver.sharedObserver
|
|
websocketTaskMessageSendCompletion:message
|
|
error:error
|
|
];
|
|
};
|
|
|
|
((void(*)(id, SEL, id, id))objc_msgSend)(
|
|
slf, swizzledSelector, message, completion
|
|
);
|
|
};
|
|
|
|
[FLEXUtility replaceImplementationOfKnownSelector:selector
|
|
onClass:cls
|
|
withBlock:implementationBlock
|
|
swizzledSelector:swizzledSelector
|
|
];
|
|
}
|
|
|
|
+ (void)injectWebsocketReceiveMessage:(Class)cls API_AVAILABLE(ios(13.0)) {
|
|
SEL selector = @selector(receiveMessageWithCompletionHandler:);
|
|
SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector];
|
|
|
|
typedef void (^SendMessageBlock)(
|
|
NSURLSessionWebSocketTask *slf,
|
|
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
|
|
);
|
|
|
|
SendMessageBlock implementationBlock = ^(
|
|
NSURLSessionWebSocketTask *slf,
|
|
void (^completion)(NSURLSessionWebSocketMessage *message, NSError *error)
|
|
) {
|
|
id completionHook = ^(NSURLSessionWebSocketMessage *message, NSError *error) {
|
|
[FLEXNetworkObserver.sharedObserver
|
|
websocketTask:slf receiveMessagage:message error:error
|
|
];
|
|
completion(message, error);
|
|
};
|
|
|
|
((void(*)(id, SEL, id))objc_msgSend)(
|
|
slf, swizzledSelector, completionHook
|
|
);
|
|
|
|
};
|
|
|
|
[FLEXUtility replaceImplementationOfKnownSelector:selector
|
|
onClass:cls
|
|
withBlock:implementationBlock
|
|
swizzledSelector:swizzledSelector
|
|
];
|
|
}
|
|
|
|
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];
|
|
requestState.dataAccumulator = [NSMutableData new];
|
|
|
|
[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))completionHandler
|
|
delegate:(id<NSURLSessionDelegate>)delegate {
|
|
[self performBlock:^{
|
|
NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask];
|
|
FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
|
|
requestState.dataAccumulator = [NSMutableData new];
|
|
|
|
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) {
|
|
requestState.dataAccumulator = [NSMutableData new];
|
|
[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
|
|
];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
|
|
sendMessagage:(NSURLSessionWebSocketMessage *)message {
|
|
[self performBlock:^{
|
|
// NSString *requestID = [[self class] requestIDForConnectionOrTask:task];
|
|
[FLEXNetworkRecorder.defaultRecorder recordWebsocketMessageSend:message task:task];
|
|
}];
|
|
}
|
|
|
|
- (void)websocketTaskMessageSendCompletion:(NSURLSessionWebSocketMessage *)message
|
|
error:(NSError *)error {
|
|
[self performBlock:^{
|
|
[FLEXNetworkRecorder.defaultRecorder
|
|
recordWebsocketMessageSendCompletion:message
|
|
error:error
|
|
];
|
|
}];
|
|
}
|
|
|
|
- (void)websocketTask:(NSURLSessionWebSocketTask *)task
|
|
receiveMessagage:(NSURLSessionWebSocketMessage *)message
|
|
error:(NSError *)error {
|
|
[self performBlock:^{
|
|
if (!error && message) {
|
|
[FLEXNetworkRecorder.defaultRecorder
|
|
recordWebsocketMessageReceived:message
|
|
task:task
|
|
];
|
|
}
|
|
}];
|
|
}
|
|
|
|
@end
|