NSURLProtocol
NSURLProtocol 是 iOS里面的URL Loading System的一部分,URL loading system 原生已經(jīng)支持了 http,https,file,ftp,data 這些常見協(xié)議,當(dāng)然也允許我們定義自己的 protocol 去擴(kuò)展,或者定義自己的協(xié)議。當(dāng)URL loading system通過 NSURLRequest 對象進(jìn)行請求時(shí),將會自動創(chuàng)建 NSURLProtocol (是一個(gè)對象)的實(shí)例(可以是自定義的),這樣我們就有機(jī)會對該請求進(jìn)行處理。
- 可以攔截
UIWebView和WKWebView(需額外處理),基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請求。 - 忽略網(wǎng)絡(luò)請求,直接返回自定義的
Response - 修改
request(請求地址,認(rèn)證信息等等) - 返回?cái)?shù)據(jù)攔截
對URL Loading System不清楚的,可以看看下面這張圖,看看里面有哪些類:

image.png
使用NSURLProtocol的主要可以分為5個(gè)步驟:
注冊—>攔截—>轉(zhuǎn)發(fā)—>回調(diào)—>結(jié)束
NSURLProtocol的創(chuàng)建
@interface CFTHTTPProtocol : NSURLProtocol
@end
- 首先是繼承系統(tǒng)的
NSURLProtocol:
[NSURLProtocol registerClass:[CFTHTTPProtocol class]];
- 然后在
application:didFinishLaunchingWithOptions:方法中注冊,一旦注冊完畢后,它就有機(jī)會來處理所有交付給URL Loading system的網(wǎng)絡(luò)請求。
子類NSURLProtocol必須實(shí)現(xiàn)的方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
- 攔截,這個(gè)方法是自定義
protocol的入口,如果不打算處理,返回NO,如果你需要對自己關(guān)注的請求進(jìn)行處理則返回YES,這樣,URL loading system將會把本次請求的操作都給了你這個(gè)protocol。
這里有個(gè)需要注意的地方,想象一下,當(dāng)你去加載一個(gè)URL資源的時(shí)候,URL Loading System會詢問CustomURLProtocol是否能處理該請求,你返回YES,然后URL Loading System會創(chuàng)建一個(gè)CustomURLProtocol實(shí)例然后調(diào)用NSURLSession去獲取數(shù)據(jù),然而這也會調(diào)用URL Loading System,而你在+canInitWithRequest:中又總是返回YES,這樣URL Loading System又會創(chuàng)建一個(gè)CustomURLProtocol實(shí)例導(dǎo)致無限循環(huán)。我們應(yīng)該保證每個(gè)request只被處理一次,可以通過+setProperty:forKey:inRequest:標(biāo)示那些已經(jīng)處理過的request,然后在+canInitWithRequest:中查詢該request是否已經(jīng)處理過了,如果是則返回NO。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
- 這個(gè)方法主要是用來返回格式化好的
request,如果自己沒有特殊需求的話,直接返回當(dāng)前的request就好了。如果你想做些其他的,比如地址重定向,或者請求頭的重新設(shè)置,你可以copy下這個(gè)request然后進(jìn)行設(shè)置并返回一個(gè)新的request,這是一個(gè)抽象方法,子類必須實(shí)現(xiàn)。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
- 這個(gè)方法用于判斷你的自定義reqeust是否相同,這里返回默認(rèn)實(shí)現(xiàn)即可。它的主要應(yīng)用場景是某些直接使用緩存而非再次請求網(wǎng)絡(luò)的地方。
- (void)startLoading;
- (void)stopLoading;
- 這兩個(gè)方法主要是開始和取消相應(yīng)的request,而且需要標(biāo)示那些已經(jīng)處理過的request。
實(shí)現(xiàn)NSURLSessionDataDelegate和NSURLSessionTaskDelegate
回調(diào),如果你對你關(guān)注的請求進(jìn)行了攔截,那么你就需要通過實(shí)現(xiàn) NSURLProtocolClient 這個(gè)協(xié)議的對象將消息轉(zhuǎn)給URL loading system,也就是 NSURLProtocol 中的 client 這個(gè)對象。看看這個(gè) 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;
你會發(fā)現(xiàn)和 NSURLSessionDelegate 很像,其實(shí)就是做了個(gè)轉(zhuǎn)發(fā)的操作。