1. NSURLProtocol類
NSURLProtocol能夠讓你去重新定義蘋果的URL加載系統(tǒng) (URL Loading System)的行為抚恒,URL Loading System里有許多類用于處理URL請(qǐng)求,比如NSURL蛤奢,NSURLRequest,NSURLConnection和NSURLSession等
可以攔截的網(wǎng)絡(luò)請(qǐng)求包括NSURLSession陶贼,NSURLConnection以及UIWebVIew,基于CFNetwork的網(wǎng)絡(luò)請(qǐng)求啤贩,以及WKWebView的請(qǐng)求是無(wú)法攔截的,WKWebView基于Webkit。
NSURLProtocol是一個(gè)抽象類拜秧,不能直接創(chuàng)建使用痹屹,需要繼承使用,使用的時(shí)候需要調(diào)用registerClass:枉氮, [NSURLProtocol registerClass:[KCURLProtocol class]]
NSURLProtocol用處:
- 重定向網(wǎng)絡(luò)請(qǐng)求(可以解決電信的DNS域名劫持問(wèn)題)
- 忽略網(wǎng)絡(luò)請(qǐng)求志衍,使用本地緩存
- 自定義網(wǎng)絡(luò)請(qǐng)求的返回結(jié)果Response
- 攔截圖片加載請(qǐng)求,轉(zhuǎn)為從本地文件加載
- 一些全局的網(wǎng)絡(luò)請(qǐng)求設(shè)置
- 快速進(jìn)行測(cè)試環(huán)境的切換
- 過(guò)濾掉一些非法請(qǐng)求
- 網(wǎng)絡(luò)的緩存處理(H5離線包 和 網(wǎng)絡(luò)圖片緩存)
- 可以攔截UIWebView聊替,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請(qǐng)求楼肪。目前WKWebView無(wú)法被NSURLProtocol攔截。
- 當(dāng)有多個(gè)自定義NSURLProtocol注冊(cè)到系統(tǒng)中的話惹悄,會(huì)按照他們注冊(cè)的反向順序依次調(diào)用URL加載流程春叫。當(dāng)其中有一個(gè)NSURLProtocol攔截到請(qǐng)求的話,后續(xù)的NSURLProtocol就無(wú)法攔截到該請(qǐng)求泣港。
2. NSURLProtocol的使用主要是5個(gè)步驟:
- 注冊(cè)
[NSURLProtocol registerClass:[KCURLProtocol class]];
- 攔截
NSURLConnection UIWebview請(qǐng)求攔截
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
NSURLSession的task攔截
+ (BOOL)canInitWithTask:(NSURLSessionTask *)task;
這兩個(gè)方法是注冊(cè)后,NSURLProtocol就會(huì)通過(guò)這個(gè)方法確定參數(shù)request象缀、task是否需要被處理,YES需要被處理爷速,NO不需要被處理,可以做重定向霞怀、監(jiān)聽(tīng)
- 轉(zhuǎn)發(fā)
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
通過(guò)該方法你可以簡(jiǎn)單的直接返回request惫东,但可以在這里修改request,比如修改header,修改host等廉沮,并返回一個(gè)新的request颓遏,這是一個(gè)抽象方法,子類必須實(shí)現(xiàn)
- (void)startLoading;
把處理過(guò)的request重新發(fā)送出去
4.回調(diào)
這四個(gè)方法來(lái)回調(diào)給原來(lái)發(fā)送網(wǎng)絡(luò)請(qǐng)求的地方滞时。
[self.client URLProtocol:self didFailWithError:error];
[self.client URLProtocolDidFinishLoading:self];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
- 結(jié)束
- (void)stopLoading;
[NSURLProtocol unregisterClass:[KCURLProtocol class]];
根據(jù)request從系統(tǒng)的緩存中構(gòu)建protocol叁幢,沒(méi)有緩存可能為nil,
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
- (instancetype)initWithTask:(NSURLSessionTask *)task cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
處理過(guò)的請(qǐng)求可以通過(guò)為請(qǐng)求設(shè)置關(guān)聯(lián),下次不再處理坪稽,防止無(wú)限循環(huán)
+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request
判斷2個(gè)請(qǐng)求是否相等曼玩,相等的話可以使用緩存數(shù)據(jù)
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
3. 攔截重定向演示
3.1 UIWebView 攔截
KCURLProtocol中:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
// 攔截百度的logo
if ([[request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
return YES;
}
return NO;
}
- (void)startLoading
{
// 攔截改成自己數(shù)據(jù)
if ([[self.request.URL absoluteString] isEqualToString:@"https://m.baidu.com/static/index/plus/plus_logo_web.png"]) {
NSString *fileName = [[NSBundle mainBundle] pathForResource:@"lufei.jpg" ofType:@""];
NSData *data = [NSData dataWithContentsOfFile:fileName];
[self.client URLProtocol:self didLoadData:data];//回調(diào)回去
}
}
3.2 NSURLSession 攔截
NSURLSession需要給NSURLSessionConfiguration對(duì)象的protocolClasses屬性添加自己的協(xié)議,把系統(tǒng)的也加回去
- (void)startSessionHook
{
NSString *url = @"http://www.baidu.com";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:6];
[arr addObject:[NSClassFromString(@"KCURLProtocol") class]];
[arr addObjectsFromArray:config.protocolClasses];
config.protocolClasses = arr;
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
+ (BOOL)canInitWithTask:(NSURLSessionTask *)task
{
NSLog(@"task---%@",task);
if ([task.originalRequest.URL.absoluteString isEqualToString:@"http://www.baidu.com"]) {
return YES;
}
return NO;
}
//轉(zhuǎn)發(fā)
- (void)startLoading{
NSMutableURLRequest *request = [self.request mutableCopy];
if ([request.URL.absoluteString isEqualToString:@"http://www.baidu.com"]) {
request.URL = [NSURL URLWithString:@"http://www.reibang.com/"];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:mainQueue];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
completionHandler(NSURLSessionResponseAllow);
}
//回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
// 打印返回?cái)?shù)據(jù)
NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (dataStr) {
NSLog(@"***截取數(shù)據(jù)***: %@", dataStr);
}
[self.client URLProtocol:self didLoadData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
[self.client URLProtocol:self didFailWithError:error];
} else {
[self.client URLProtocolDidFinishLoading:self];
}
}
3.3 AFN 攔截
AFN也是基于NSURLSession窒百,我們可以新建一個(gè)NSURLSession的子類黍判,在load方法里面hook protocolClasses的getter方法,這樣所有的基于session的都能攔截測(cè)試實(shí)際上是hook __NSCFURLSessionConfiguration的getter方法篙梢,hook NSURLSessionConfiguration的getter不管用
+ (void)load
{
Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");
Method originalMethod = class_getInstanceMethod(cls, @selector(protocolClasses));
method_setImplementation(originalMethod, class_getMethodImplementation(self, @selector(my_protocolClasses)));
}
- (NSArray<Class> *)my_protocolClasses
{
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:[NSClassFromString(@"KCURLProtocol") class], [NSClassFromString(@"_NSURLHTTPProtocol") class],[NSClassFromString(@"_NSURLDataProtocol") class],[NSClassFromString(@"_NSURLFTPProtocol") class],[NSClassFromString(@"_NSURLFileProtocol") class],[NSClassFromString(@"NSAboutURLProtocol") class],nil];
return arr;
}
4. 多個(gè)NSURLProtocol
如果注冊(cè)了兩個(gè)NSURLProtocol,protocols的遍歷是反向的顷帖,也就是最后注冊(cè)的protocol會(huì)被優(yōu)先判斷。