一、前言
NSURLProtocol是iOS中URL Loading System的一部分。如果開發者自定義的一個NSURLProtocol並且註冊到app中,那麼在這個自定義的NSURLProtocol中我們可以攔截UIWebView,基於系統的NSURLConnection或者NSURLSession進行封裝的網路請求,然後做到自定義的response返回。非常強大。
二、NSURLProtocol的使用流程
2.1、在AppDelegate中註冊自定義的NSURLProtocol。
比如我這邊自定義的NSURLProtocol叫做YXNSURLProtocol。
1 2 |
@interface YXNSURLProtocol : NSURLProtocol @end |
在系統載入的時候,把自定義的YXNSURLProtocol註冊到URL載入系統中,這樣 所有的URL請求都有機會進入我們自定義的YXNSURLProtocol進行攔截處理。
1 2 3 |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[YXNSURLProtocol class]]; } |
載入完成後,當產生URL請求的同時,會依次進入NSURLProtocol的以下相關方法進行處理,下面我們依次來講一下每一個方法的作用。
2.2、NSURLProtocol中的幾個方法
2.2.1、是否進入自定義的NSURLProtocol載入器
1 2 3 4 5 6 7 8 |
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{ BOOL intercept = YES; NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString); if (intercept) { } return intercept; } |
如果返回YES則進入該自定義載入器進行處理,如果返回NO則不進入該自定義選擇器,使用系統預設行為進行處理。
如果這一步驟返回YES。則會進入2.3的方法中。
2.2.2、重新設定NSURLRequest的資訊
1 2 3 |
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } |
在這個方法中,我們可以重新設定或者修改request的資訊。比如請求重定向或者新增頭部資訊等等。如果沒有特殊需求,直接返回request就可以了。但是因為這個方法在會在一次請求中被呼叫多次(暫時我也不知道什麼原因為什麼需要回撥多洗),所以request重定向和新增頭部資訊也可以在開始載入中startLoading方法中重新設定。
2.2.3、這個方法主要是用來判斷兩個request是否相同,如果相同的話可以使用快取資料,通常呼叫父類的實現即可
1 2 3 |
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{ return [super requestIsCacheEquivalent:a toRequest:b]; } |
這個方法基本不常用。
2.2.4、被攔截的請求開始執行的地方
1 2 |
- (void)startLoading{ } |
這個函式使我們重點使用的函式。
2.2.5、結束載入URL請求
1 2 |
- (void)stopLoading{ } |
2.3、NSURLProtocolClient中的幾個方法
上面的NSURLProtocol定義了一系列載入的流程。而在每一個流程中,我們作為使用者該如何使用URL載入系統,則是NSURLProtocolClient中幾個方法該做的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@protocol NSURLProtocolClient //請求重定向 - (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse; // 響應快取是否合法 - (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse; //剛接收到Response資訊 - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy; //資料載入成功 - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data; //資料完成載入 - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol; //資料載入失敗 - (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error; //為指定的請求啟動驗證 - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; //為指定的請求取消驗證 - (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; @end |
三、實現一個地址重定向的Demo
這個Demo實現的功能是在UIWebView中所有跳轉到sina首頁的請求,都重定位到sohu首頁。
3.1、第一步,新建一個UIWebView,載入sina首頁
1 2 3 4 5 6 |
_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
1 2 3 |
@interface YXNSURLProtocolTwo : NSURLProtocol @end |
3.3、在AppDelegate中,進行註冊
1 2 3 4 |
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[YXNSURLProtocolTwo class]]; return YES; } |
3.4、在canInitWithRequest方法中攔截https://sina.cn/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{ NSLog(@"canInitWithRequest url-->%@",request.URL.absoluteString); //看看是否已經處理過了,防止無限迴圈 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中進行方法重定向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
- (void)startLoading{ NSMutableURLRequest * request = [self.request mutableCopy]; // 標記當前傳入的Request已經被攔截處理過, //防止在最開始又繼續攔截處理 [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; } |
你也可以選擇在+ (NSURLRequest )canonicalRequestForRequest:(NSURLRequest )request
替換request。效果都是一樣的。
3.6、因為新建了一個NSURLConnection *connection,所以要實現他的代理方法,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (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]; } |
通過以上幾步,我們就可以實現最簡單的url重定向,WebView載入新浪首頁,卻跳轉到了搜狐首頁。
四、小結
通過自定義的NSURLProtocol,我們拿到使用者請求的request之後,我們可以做很多事情。比如:
1、自定義請求和響應
2、網路的快取處理(H5離線包 和 網路圖片快取)
3、重定向網路請求
4、為測試提供資料Mocking功能,在沒有網路的情況下使用本地資料返回。
5、過濾掉一些非法請求
6、快速進行測試環境的切換
8、可以攔截UIWebView,基於系統的NSURLConnection或者NSURLSession進行封裝的網路請求。目前WKWebView無法被NSURLProtocol攔截。
9、當有多個自定義NSURLProtocol註冊到系統中的話,會按照他們註冊的反向順序依次呼叫URL載入流程。當其中有一個NSURLProtocol攔截到請求的話,後續的NSURLProtocol就無法攔截到該請求。
五、聯絡方式
歡迎加好友、一起交流。