詳解如何攔截iOS所有網絡請求


https://www.jb51.net/article/141692.htm

最近在研究iOS無埋點統計技術,我們的統計SDK主要分兩部分:點擊事件和網絡請求。統計所有的點擊事件是采用Method Swizzling實現的,可以做到使用中不需要一行代碼實現統計所有事件,具體細節將來我會專門抽幾篇文章介紹。

今天主要說說如何統計APP中的所有網絡請求。公司網絡請求如果不是靜態庫或者框架,很容易想到在網絡請求發送和返回時添加統計的代碼。如何在不修改原來代碼(或者修改最少)的基礎上攔截所有的請求呢,能不能從系統層面上攔截回調呢?答案是肯定的,蘋果有一個黑魔法NSURLProtocol

介紹

NSURLProtocol是iOS URL Loading System中的一部分,看起來像是一個協議,但其實這是一個類,而且必須使用該類的子類,並且需要被注冊。先看看他在URL Loading System中的位置:

使用場景

不管是UIWebView還是URLSession還是第三方的AFNetWorkongAlamofire或者SDWebImage他們都是基於URLSession或者NSURLConnection來實現的,因此可以通過NSURLProtocol做自定義操作。

  1. 重定向網絡請求
  2. 攔截網絡加載,采用本地緩存
  3. 修改Request信息
  4. 自定義返回結果
  5. 對請求進行HTTPDNS解析,動態設置Host,解決不同網絡下客戶端不能訪問的情況

實現

首先要繼承NSURLProtocol創建自定義的類,然后重寫startLoading、stopLoading添加我們的統計代碼就可以了:

1
2
3
static NSString * const hasInitKey = @ "LLMarkerProtocolKey" ;
@interface LLMarkerURLProtocol : NSURLProtocol
@end

子類實現的NSURLProtocol方法:

1.0 +(BOOL)canInitWithRequest:(NSURLRequest *)request;子類是否能響應該請求。

1
2
3
4
5
6
+( BOOL )canInitWithRequest:(NSURLRequest *)request{
  if ([NSURLProtocol propertyForKey:hasInitKey inRequest:request]) {
   return NO;
  }
  return YES;
}

2.0  +(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;自定義網絡請求,如果不需要處理直接返回request。

1
2
3
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
  return request;
}

3.0  -(void)startLoading 開始網絡請求,需要在該方法中發起一個請求,對於NSURLConnection來說,就是創建一個NSURLConnection,對於NSURLSession,就是發起一個NSURLSessionTask 。一般下載前需要設置該請求正在進行下載,防止多次下載的情況發生。

1
2
3
4
5
6
-( void )startLoading{
  NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
  //做下標記,防止遞歸調用
  [NSURLProtocol setProperty:@YES forKey:hasInitKey inRequest:mutableReqeust];
  self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}

4.0  -(void)stopLoading 停止相應請求,清空請求Connection 或Task。

1
2
3
-( void )stopLoading{
  [self.connection cancel];
}

5.0 實現NSURLConnectionDelegateNSURLConnectionDataDelegate或者NSURLSessionTaskDelegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma mark - NSURLConnectionDelegate
 
-( void )connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
  [self.client URLProtocol:self didFailWithError:error];
}
#pragma mark - NSURLConnectionDataDelegate
- ( void )connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
  self.responseData = [[NSMutableData alloc] init];
  [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
 
- ( void )connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  [self.responseData appendData:data];
  [self.client URLProtocol:self didLoadData:data];
}
 
- ( void )connectionDidFinishLoading:(NSURLConnection *)connection {
  [self.client URLProtocolDidFinishLoading:self];
}

使用

一、在AppDelegate中注冊:

1
[NSURLProtocol registerClass:[LLMarkerURLProtocol class ]];

這樣能攔截UIWebView和自定義的請求了,如果要攔截AFNetWorking、Alamofire等第三方請求還需要做一些修改。

二、LLMarkerURLProtocol中添加自定義NSURLSessionConfiguration方法:

1
2
3
4
5
6
7
+ (NSURLSessionConfiguration *) defaultSessionConfiguration{
  NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
  NSMutableArray *array = [[config protocolClasses] mutableCopy];
  [array insertObject:[self class ] atIndex:0];
  config.protocolClasses = array;
  return config;
}

攔截第三方網絡庫方法就是讓第三方使用我們這個NSURLSessionConfiguration。因為我們在自己的NSURLSessionConfiguration 中的protocolClasses中注冊了自己類。

三、 下面以Alamofire為例

1.0 繼承Alamofire.SessionManager 自定義SessionManager

1
2
3
4
5
6
7
8
class LLSessionManger: Alamofire.SessionManager{
  public static let sharedManager: SessionManager = {
   let configuration = LLMarkerURLProtocol.defaultSessionConfiguration()
   configuration?.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
   let manager = Alamofire.SessionManager(configuration: configuration!)
   return manager
  }()
}

2.0 使用 LLSessionManger進行網絡請求

1
2
3
4
5
6
let manager = LLSessionManger.sharedManager
manager.request( "https://httpbin.org/get" ).responseJSON { (response) in
  if let JSON = response.result.value {
   print( "JSON: \(JSON)" )
  }
}

注意:AFNetWorking、SDWebimage等第三方庫的修改和Alamofire類似,找到使用NSURLSessionConfiguration的地方,換成LLMarkerURLProtocol的defaultSessionConfiguration就可以了。

看到這你可能發現,如果使用Alamofire進行網絡請求,我們還是修改了原來的代碼,下篇文章單獨介紹如何不修改原來代碼,通過注冊Alamofire通知方式,攔截Alamofire的網絡請求。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

程序猿的那些事、送書等活動等着你

原文鏈接:https://www.jianshu.com/p/b607cbd90503

也許是最全java資料!(文檔+項目+資料)【點擊下載】 和努力的人一起學習Java!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM