網絡層的數據,通常要收集的是API的請求頻率、API請求時間、成功率等等信息。若是經過無埋的方式收集網絡信息,確定是經過AOP的方式,hook相應的方法和相應的delegate方法,來實現這一需求。git
首先來分析一下經過NSURLSession發起的網絡請求的流程:NSURLSession實際發起網絡請求,是根據響應生成的[task resume]來開始網絡請求的。github
而後NSURLSession提供了兩種方式來對請求的回調進行處理,一種是經過delegate來進行處理,還有一種就是經過block的方式,直接回調請求結果。cookie
經過delegate回調方式來進行網絡請求回調的處理,AFNetWorking經過NSURLSession發起的網絡請求就是經過delegate來處理的,只是對外暴露的咱們常用的是block的方式。網絡
看一下NSURLSession的初始化方式和設置delegate的方式session
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
複製代碼
提供的是兩個類構造器,從上面兩個構造的參數,咱們可以猜出來,其實sessionWithConfiguration:最終也是調用sessionWithConfiguration:delegate:delegateQueu:方法,來初始化。通常咱們把sessionWithConfiguration:delegate:delegateQueu:叫作工廠類方法。app
還有一個方法,咱們也常常用來獲取session的實例,就是sharedSession,那這個獲取的session和兩個類構造器獲取的session有什麼不一樣呢? 其實咱們在初始化session的時候,不管調用哪個類構造器初始化session時,sharedSession都會調用sessionWithConfiguration:方法初始化一個單例session,可是這個單例的session有許多的限制,好比cookie、cache等,具體的說明,詳見developer.apple.com/documentati…異步
什麼意思呢?上面這麼長一句。意思就是說,若是咱們初始化了一個session,經過方法sessionWithConfiguration:,其實在NSURLSession內部會調用兩次這個方法,第一次是咱們主動調用生成一個session,返回給咱們,另一次就是sharedSession調用,生成一個系統默認的單例session,注意:由於這個sharedSession是一個單例的session,因此也就只有在首次生成session的時候,sharedSession會主動調用。固然,經過方法sessionWithConfiguration:delegate:delegateQueu:初始化session也是同樣的。socket
爲啥要說這麼多,由於咱們須要在session初始化的時候,作hook delegate的操做,由於NSURLSession的delegate是一個只讀的屬性,咱們只能在初始化的時候來作hook處理ui
@property (nullable, readonly, retain) id <NSURLSessionDelegate> delegate;
複製代碼
首先考慮一下,咱們有三個方法可以獲取到session的實例,其實真正有delegate的只有一個構造方法,其餘兩個方法都沒有delegate,那怎麼作呢?url
沒有delegate的session是經過block回調方式拿到請求結果的,因此咱們能夠將session的含有block回調的方法hook掉,而後經過傳入咱們本身的block就可以拿到網絡的回調結果了。
**注意:**若是一個session同時有delegate和block回調,那麼delegate是不會被觸發的,會直接回調到block裏面,由於若是沒有經過block回調來發起的請求,在session內部,實際上也是調用的含block的方法。這個在後面會詳細介紹
仍是看一下代碼吧
首先介紹hook類構造器,達到hook delegate的效果。由於須要經過delegate拿到網絡回調的類構造器只有sessionWithConfiguration:delegate:delegateQueue:方法,因此只須要將這個構造器hook掉,而後拿到delegate,而後再將delegate的對應的delegate方法hook掉就行
在NSURLSession的一個分類中,在load方法中,咱們將sessionWithConfiguration:delegate:delegateQueue: hook
Hook_Method(cls, @selector(sessionWithConfiguration:delegate:delegateQueue:), cls, @selector(hook_sessionWithConfiguration:delegate:delegateQueue:),YES);
複製代碼
具體的hook實現方法,這個方法把hook類方法和hook實例方法都放在裏面了,由於待會咱們還要hook session的實例方法
static void Hook_Method(Class originalClass, SEL originalSel, Class replaceClass, SEL replaceSel, BOOL isHookClassMethod) {
Method originalMethod = NULL;
Method replaceMethod = NULL;
if (isHookClassMethod) {
originalMethod = class_getClassMethod(originalClass, originalSel);
replaceMethod = class_getClassMethod(replaceClass, replaceSel);
} else {
originalMethod = class_getInstanceMethod(originalClass, originalSel);
replaceMethod = class_getInstanceMethod(replaceClass, replaceSel);
}
if (!originalMethod || !replaceMethod) {
return;
}
IMP originalIMP = method_getImplementation(originalMethod);
IMP replaceIMP = method_getImplementation(replaceMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *replaceType = method_getTypeEncoding(replaceMethod);
//注意這裏的class_replaceMethod方法,必定要先將替換方法的實現指向原實現,而後再將原實現指向替換方法,不然若是先替換原方法指向替換實現,那麼若是在執行完這一句瞬間,原方法被調用,這時候,替換方法的實現尚未指向原實現,那麼如今就形成了死循環
if (isHookClassMethod) {
Class originalMetaClass = objc_getMetaClass(class_getName(originalClass));
Class replaceMetaClass = objc_getMetaClass(class_getName(replaceClass));
class_replaceMethod(replaceMetaClass,replaceSel,originalIMP,originalType);
class_replaceMethod(originalMetaClass,originalSel,replaceIMP,replaceType);
} else {
class_replaceMethod(replaceClass,replaceSel,originalIMP,originalType);
class_replaceMethod(originalClass,originalSel,replaceIMP,replaceType);
}
複製代碼
而後在咱們的hook實現方法中
+ (NSURLSession *)hook_sessionWithConfiguration: (NSURLSessionConfiguration *)configuration delegate: (id<NSURLSessionDelegate>)delegate delegateQueue: (NSOperationQueue *)queue {
if (delegate) {
Hook_Delegate_Method([delegate class], @selector(URLSession:dataTask:didReceiveData:), [self class], @selector(hook_URLSession:dataTask:didReceiveData:), @selector(none_URLSession:dataTask:didReceiveData:));
}
return [self hook_sessionWithConfiguration: configuration delegate: delegate delegateQueue: queue];
}
複製代碼
一樣的,hook delegate的方法
//hook delegate方法
static void Hook_Delegate_Method(Class originalClass, SEL originalSel, Class replaceClass, SEL replaceSel, SEL noneSel) {
Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
Method replaceMethod = class_getInstanceMethod(replaceClass, replaceSel);
if (!originalMethod) {//沒有實現delegate 方法
Method noneMethod = class_getInstanceMethod(replaceClass, noneSel);
BOOL didAddNoneMethod = class_addMethod(originalClass, originalSel, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));
if (didAddNoneMethod) {
NSLog(@"沒有實現的delegate方法添加成功");
}
return;
}
BOOL didAddReplaceMethod = class_addMethod(originalClass, replaceSel, method_getImplementation(replaceMethod), method_getTypeEncoding(replaceMethod));
if (didAddReplaceMethod) {
NSLog(@"hook 方法添加成功");
Method newMethod = class_getInstanceMethod(originalClass, replaceSel);
method_exchangeImplementations(originalMethod, newMethod);
}
}
複製代碼
注意 這裏有一個地方須要注意,若是咱們要hook的delegate有些方法沒有實現,可是咱們又想要hook掉這個方法,那麼就須要先將delegate沒有實現的方法 將它先添加進去,而後再將這個方法替換掉
而後在咱們的替換類中實現相應的替換方法便可
- (void)hook_URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
[self hook_URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)none_URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
NSLog(@"11");
}
複製代碼
若是session沒有經過delegate去拿到回調,那咱們這時候須要怎麼作呢?
若是不經過delegate拿,那就是session中一系列的含block的請求方法了,這些被稱爲 異步便利請求方法,所有定義在NSURLSession的一個分類中 NSURLSession (NSURLSessionAsynchronousConvenience)
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
...
複製代碼
這裏就舉一個例子,來展現一下怎麼hook 帶block 參數的方法,其實也就是構造一個和參數同樣的block,將本身的block傳進去
一樣的仍是先將方法替換掉
Hook_Method(cls, @selector(dataTaskWithRequest:completionHandler:), cls, @selector(hook_dataTaskWithRequest:completionHandler:),NO);
複製代碼
而後,在咱們hook的方法中
- (NSURLSessionDataTask *)hook_dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler {
NSLog(@"33");
void (^customBlock)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (completionHandler) {
completionHandler(data,response,error);
}
//作本身的處理
};
if (completionHandler) {
return [self hook_dataTaskWithRequest:request completionHandler:customBlock];
} else {
return [self hook_dataTaskWithRequest:request completionHandler:nil];
}
}
複製代碼
注意 這裏須要判斷當前的block是否存在,由於當咱們將這個方法hook了之後,若是是當前的session是須要經過delegate來進行網絡回調的,可是請求仍是會走到咱們hook的方法中,由於在session內部實現,我猜想應該是作了相似工廠方法的處理
因此這裏判斷若是block回調爲空的時候,直接將nil傳進去,這樣就可以經過delegate拿到回調結果了
這裏就簡單舉了一個帶block參數的hook 其餘的方法處理方式也是相似的,這裏就再也不一一列舉了
這一篇主要講的是hook系統的默認的http的請求方法,由於NSURLConnection已經廢棄了,因此就沒有作這個的hook,不過實現方式也是相似的
下一篇,咱們將講一下socket的hook,而後就再到view的圈選等等,這個系列會將無埋的一些主要的處理方式都分享出來。
另外:以前作這個hook的方式以前,也使用過NSURLProtocol來進行一些網絡處理的攔截,可是由於涉及到多protocol的問題,由於目前項目中已經使用到了多個protocol,因此這種方式就拋棄了。並且,根據以前作的處理,NSURLProtocol要作的工做也不比這個少,因此就採用了AOP的方式
歡迎關注:
聯繫我: ppsheep.qian@gmail.com