NSURLProtocol
NSURLProtocol
是 iOS里面的URL Loading System的一部分,URL loading system 原生已經支持了 http,https,file,ftp,data
這些常見協議,當然也允許我們定義自己的 protocol
去擴展,或者定義自己的協議。當URL loading system通過 NSURLRequest
對象進行請求時,將會自動創建 NSURLProtocol
(是一個對象)的實例(可以是自定義的),這樣我們就有機會對該請求進行處理。
- 可以攔截
UIWebView
和WKWebView
(需額外處理),基于系統的NSURLConnection
或者NSURLSession
進行封裝的網絡請求。 - 忽略網絡請求,直接返回自定義的
Response
- 修改
request
(請求地址,認證信息等等) - 返回數據攔截
對URL Loading System不清楚的,可以看看下面這張圖,看看里面有哪些類:
image.png
使用NSURLProtocol的主要可以分為5個步驟:
注冊—>攔截—>轉發—>回調—>結束
NSURLProtocol的創建
@interface CFTHTTPProtocol : NSURLProtocol
@end
- 首先是繼承系統的
NSURLProtocol
:
[NSURLProtocol registerClass:[CFTHTTPProtocol class]];
- 然后在
application:didFinishLaunchingWithOptions:
方法中注冊,一旦注冊完畢后,它就有機會來處理所有交付給URL Loading system的網絡請求。
子類NSURLProtocol必須實現的方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
- 攔截,這個方法是自定義
protocol
的入口,如果不打算處理,返回NO,如果你需要對自己關注的請求進行處理則返回YES,這樣,URL loading system將會把本次請求的操作都給了你這個protocol
。
這里有個需要注意的地方,想象一下,當你去加載一個URL資源的時候,URL Loading System會詢問CustomURLProtocol
是否能處理該請求,你返回YES,然后URL Loading System會創建一個CustomURLProtocol
實例然后調用NSURLSession
去獲取數據,然而這也會調用URL Loading System,而你在+canInitWithRequest:
中又總是返回YES,這樣URL Loading System又會創建一個CustomURLProtocol
實例導致無限循環。我們應該保證每個request只被處理一次,可以通過+setProperty:forKey:inRequest:
標示那些已經處理過的request,然后在+canInitWithRequest:
中查詢該request是否已經處理過了,如果是則返回NO。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
- 這個方法主要是用來返回格式化好的
request
,如果自己沒有特殊需求的話,直接返回當前的request
就好了。如果你想做些其他的,比如地址重定向,或者請求頭的重新設置,你可以copy下這個request
然后進行設置并返回一個新的request
,這是一個抽象方法,子類必須實現。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- 這個方法用于判斷你的自定義reqeust是否相同,這里返回默認實現即可。它的主要應用場景是某些直接使用緩存而非再次請求網絡的地方。
- (void)startLoading;
- (void)stopLoading;
- 這兩個方法主要是開始和取消相應的request,而且需要標示那些已經處理過的request。
實現NSURLSessionDataDelegate和NSURLSessionTaskDelegate
回調,如果你對你關注的請求進行了攔截,那么你就需要通過實現 NSURLProtocolClient
這個協議的對象將消息轉給URL loading system,也就是 NSURLProtocol
中的 client
這個對象。看看這個 NSURLProtocolClient
里面的方法:
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
- (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 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
你會發現和 NSURLSessionDelegate
很像,其實就是做了個轉發的操作。