https://www.jb51.net/article/141692.htm
最近在研究iOS無埋點
統計技術,我們的統計SDK
主要分兩部分:點擊事件和網絡請求。統計所有的點擊事件是采用Method Swizzling
實現的,可以做到使用中不需要一行代碼實現統計所有事件,具體細節將來我會專門抽幾篇文章介紹。
今天主要說說如何統計APP中的所有網絡請求。公司網絡請求如果不是靜態庫
或者框架
,很容易想到在網絡請求發送和返回時添加統計的代碼。如何在不修改原來代碼(或者修改最少)的基礎上攔截所有的請求呢,能不能從系統層面上攔截回調呢?答案是肯定的,蘋果有一個黑魔法NSURLProtocol
。
介紹
NSURLProtocol是iOS URL Loading System中的一部分,看起來像是一個協議,但其實這是一個類,而且必須使用該類的子類,並且需要被注冊。先看看他在URL Loading System中的位置:
使用場景
不管是UIWebView還是URLSession還是第三方的AFNetWorkong
、Alamofire
或者SDWebImage
他們都是基於URLSession
或者NSURLConnection
來實現的,因此可以通過NSURLProtocol做自定義操作。
- 重定向網絡請求
- 攔截網絡加載,采用本地緩存
- 修改Request信息
- 自定義返回結果
- 對請求進行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 實現NSURLConnectionDelegate
、NSURLConnectionDataDelegate
或者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
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!