一、前言
NSURLProtocol是iOS中URL Loading System的一部分。如果開發(fā)者自定義的一個NSURLProtocol并且注冊到app中,那么在這個自定義的NSURLProtocol中我們可以攔截UIWebView,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請求,然后做到自定義的response返回。非常強大。
二、NSURLProtocol的使用流程
2.1、在AppDelegate中注冊自定義的NSURLProtocol。
比如我這邊自定義的NSURLProtocol叫做YXNSURLProtocol。
@interface YXNSURLProtocol : NSURLProtocol
@end
在系統(tǒng)加載的時候,把自定義的YXNSURLProtocol注冊到URL加載系統(tǒng)中,這樣 所有的URL請求都有機會進(jìn)入我們自定義的YXNSURLProtocol進(jìn)行攔截處理。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[YXNSURLProtocol class]];
}
加載完成后,當(dāng)產(chǎn)生URL請求的同時,會依次進(jìn)入NSURLProtocol的以下相關(guān)方法進(jìn)行處理,下面我們依次來講一下每一個方法的作用。
2.2、NSURLProtocol中的幾個方法
2.2.1、是否進(jìn)入自定義的NSURLProtocol加載器
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
BOOL intercept = YES;
NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString);
if (intercept) {
}
return intercept;
}
如果返回YES則進(jìn)入該自定義加載器進(jìn)行處理,如果返回NO則不進(jìn)入該自定義選擇器,使用系統(tǒng)默認(rèn)行為進(jìn)行處理。
如果這一步驟返回YES。則會進(jìn)入2.3的方法中。
2.2.2、重新設(shè)置NSURLRequest的信息
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
在這個方法中,我們可以重新設(shè)置或者修改request的信息。比如請求重定向或者添加頭部信息等等。如果沒有特殊需求,直接返回request就可以了。但是因為這個方法在會在一次請求中被調(diào)用多次(暫時我也不知道什么原因為什么需要回調(diào)多洗),所以request重定向和添加頭部信息也可以在開始加載中startLoading方法中重新設(shè)置。
2.2.3、這個方法主要是用來判斷兩個request是否相同,如果相同的話可以使用緩存數(shù)據(jù),通常調(diào)用父類的實現(xiàn)即可
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
return [super requestIsCacheEquivalent:a toRequest:b];
}
這個方法基本不常用。
2.2.4、被攔截的請求開始執(zhí)行的地方
- (void)startLoading{
}
這個函數(shù)使我們重點使用的函數(shù)。
2.2.5、結(jié)束加載URL請求
- (void)stopLoading{
}
2.3、NSURLProtocolClient中的幾個方法
上面的NSURLProtocol定義了一系列加載的流程。而在每一個流程中,我們作為使用者該如何使用URL加載系統(tǒng),則是NSURLProtocolClient中幾個方法該做的事情。
@protocol NSURLProtocolClient <NSObject>
//請求重定向
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
// 響應(yīng)緩存是否合法
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
//剛接收到Response信息
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
//數(shù)據(jù)加載成功
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
//數(shù)據(jù)完成加載
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
//數(shù)據(jù)加載失敗
- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;
//為指定的請求啟動驗證
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
//為指定的請求取消驗證
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
@end
三、實現(xiàn)一個地址重定向的Demo
這個Demo實現(xiàn)的功能是在UIWebView中所有跳轉(zhuǎn)到sina首頁的請求,都重定位到sohu首頁。
3.1、第一步,新建一個UIWebView,加載sina首頁
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
_webView.delegate = self;
[self.view addSubview:_webView];
NSURL *url = [[NSURL alloc] initWithString:@"https://sina.cn"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[_webView loadRequest:request];
3.2、自定義一個NSURLProtocol
@interface YXNSURLProtocolTwo : NSURLProtocol
@end
3.3、在AppDelegate中,進(jìn)行注冊
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[YXNSURLProtocolTwo class]];
return YES;
}
3.4、在canInitWithRequest方法中攔截https://sina.cn/
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
NSLog(@"canInitWithRequest url-->%@",request.URL.absoluteString);
//看看是否已經(jīng)處理過了,防止無限循環(huán)
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
NSString *urlString = request.URL.absoluteString;
if([urlString isEqualToString:@"https://sina.cn/"]){
return YES;
}
return NO;
}
3.5、在startLoading中進(jìn)行方法重定向
- (void)startLoading{
NSMutableURLRequest * request = [self.request mutableCopy];
// 標(biāo)記當(dāng)前傳入的Request已經(jīng)被攔截處理過,
//防止在最開始又繼續(xù)攔截處理
[NSURLProtocol setProperty:@(YES) forKey:URLProtocolHandledKey inRequest:request];
self.connection = [NSURLConnection connectionWithRequest:[self changeSinaToSohu:request] delegate:self];
}
//把所用url中包括sina的url重定向到sohu
- (NSMutableURLRequest *)changeSinaToSohu:(NSMutableURLRequest *)request{
NSString *urlString = request.URL.absoluteString;
if ([urlString isEqualToString:@"https://sina.cn/"]) {
urlString = @"http://m.sohu.com/";
request.URL = [NSURL URLWithString:urlString];
}
return request;
}
你也可以選擇在<code>+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request</code>替換request。效果都是一樣的。
3.6、因為新建了一個NSURLConnection *connection,所以要實現(xiàn)他的代理方法,如下
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
通過以上幾步,我們就可以實現(xiàn)最簡單的url重定向,WebView加載新浪首頁,卻跳轉(zhuǎn)到了搜狐首頁。
四、小結(jié)
通過自定義的NSURLProtocol,我們拿到用戶請求的request之后,我們可以做很多事情。比如:
1、自定義請求和響應(yīng)
2、網(wǎng)絡(luò)的緩存處理(H5離線包 和 網(wǎng)絡(luò)圖片緩存)
3、重定向網(wǎng)絡(luò)請求
4、為測試提供數(shù)據(jù)Mocking功能,在沒有網(wǎng)絡(luò)的情況下使用本地數(shù)據(jù)返回。
5、過濾掉一些非法請求
6、快速進(jìn)行測試環(huán)境的切換
8、可以攔截UIWebView,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請求。目前WKWebView無法被NSURLProtocol攔截。
9、當(dāng)有多個自定義NSURLProtocol注冊到系統(tǒng)中的話,會按照他們注冊的反向順序依次調(diào)用URL加載流程。當(dāng)其中有一個NSURLProtocol攔截到請求的話,后續(xù)的NSURLProtocol就無法攔截到該請求。
五、聯(lián)系方式
歡迎加好友、一起交流。