隨說 : 用著WebView作混編交互之后,發(fā)現(xiàn)其實(shí)網(wǎng)絡(luò)的整個(gè)過程需要深入理解,才能做更多的事,于是整理了一下HTTP協(xié)議等有關(guān)的概念 , 其實(shí)這篇文章不單單說的是HTTP, 可以去看一下.一起進(jìn)步
WebView交互, 說到底還是一個(gè)網(wǎng)頁展示, 需要請求 - 需要響應(yīng), 需要遵守HTTP協(xié)議, 也會(huì)遵守TCP/IP 協(xié)議.
在IOS中, 是用了NSURLConnection這個(gè)類作為連接.發(fā)出請求,接收響應(yīng)都能用到他,當(dāng)然IOS7以后,新增的NSURLSession(建議以后都用這個(gè)類,AFN3.0都不用NSURLConnection了)
NSURLProtocol是什么?
NSURLProtocol是個(gè)抽象類,只要理解為不能直接實(shí)例化它,想用它的方法,就去繼承它.
NSURLProtocol是NSURLConnection的handle類, 它更像一套協(xié)議,如果遵守這套協(xié)議,網(wǎng)絡(luò)請求Request都會(huì)經(jīng)過這套協(xié)議里面的方法去處理.
再說簡單點(diǎn),就是對上層的URLRequest請求做攔截,并根據(jù)自己的需求場景做定制化響應(yīng)處理。

圖解 : NSURLProtocol 能在系統(tǒng)執(zhí)行 URLRequest前先去將URLRequest處理了一遍.
NSURLProtocol的方法屬性##

// 這個(gè)方法是注冊NSURLProtocol子類的方法.
+ (BOOL)registerClass:(Class)protocolClass;
// 這個(gè)方法是注冊后,NSURLProtocol就會(huì)通過這個(gè)方法確定參數(shù)request是否需要被處理
// return : YES 需要經(jīng)過這個(gè)NSURLProtocol"協(xié)議" 的處理, NO 這個(gè) 協(xié)議request不需要遵守這個(gè)NSURLProtocol"協(xié)議"
// 這個(gè)方法的左右 : 1, 篩選Request是否需要遵守這個(gè)NSURLRequest , 2, 處理http: , https等URL
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
// 這個(gè)方法就是返回request,當(dāng)然這里可以處理的需求有 : 1,規(guī)范化請求頭的信息 2, 處理DNS劫持,重定向App中所有的請求指向等
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
// 這個(gè)方法主要用來判斷兩個(gè)請求是否是同一個(gè)請求,如果是,則可以使用緩存數(shù)據(jù),通常只需要調(diào)用父類的實(shí)現(xiàn)即可,默認(rèn)為YES,而且一般不在這里做事情
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
// abstract Initializes an NSURLProtocol given request, cached response, and client.
// 開始初始化一個(gè)NSURLProtocol抽象對象, 包含請求, cachedResponse , 和建立client
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullable id <NSURLProtocolClient>)client NS_DESIGNATED_INITIALIZER;
// 需要在該方法中發(fā)起一個(gè)請求,對于NSURLConnection來說,就是創(chuàng)建一個(gè)NSURLConnection,對于NSURLSession,就是發(fā)起一個(gè)NSURLSessionTask
// 另外一點(diǎn)就是這個(gè)方法之后,會(huì)回調(diào)<NSURLProtocolClient>協(xié)議中的方法,
- (void)startLoading
// 這個(gè)方法是和start是對應(yīng)的 一般在這個(gè)方法中,斷開Connection
// 另外一點(diǎn)就是當(dāng)NSURLProtocolClient的協(xié)議方法都回調(diào)完畢后,就會(huì)開始執(zhí)行這個(gè)方法了
- (void)stopLoading
/*!
@method client
@abstract Returns the NSURLProtocolClient of the receiver.
@result The NSURLProtocolClient of the receiver.
*/
@property (nullable, readonly, retain) id <NSURLProtocolClient> client;
/*!
@method request
@abstract Returns the NSURLRequest of the receiver.
@result The NSURLRequest of the receiver.
*/
@property (readonly, copy) NSURLRequest *request;
/*!
@method cachedResponse
@abstract Returns the NSCachedURLResponse of the receiver.
@result The NSCachedURLResponse of the receiver.
*/
@property (nullable, readonly, copy) NSCachedURLResponse *cachedResponse;
<NSURLProtocolClient> 的協(xié)議方法, 一般和NSURLConnection的代理方法一起使用

隨筆 : 其實(shí)在總結(jié)的過程中,能感受到蘋果對類的一種設(shè)計(jì)思想,我也不知道怎么說,基本就是,"恰到好處"
NSURLProtocol的用法##
以加載一個(gè)webView為例 :
// 程序加載的時(shí)候
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注冊TravinURLProtocol,這一步必須要做,注冊之后,才能執(zhí)行Connection的handle,執(zhí)行handle的方法
[NSURLProtocol registerClass:[TravinURLProtocol class]];
return YES;
}
最簡單的加載一個(gè)webView,當(dāng)loadRequest:開始發(fā)送的時(shí)候,就開始執(zhí)行NSURLProtocol里面的方法
UIWebView *webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.view = webView;
NSURL *url = [NSURL URLWithString:@"http://www.itdecent.cn/p/ec5d6c204e17"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request];
開始跳進(jìn)NSURLProtocol執(zhí)行方法
static NSString *const TravinProtocolHandledKey = @"TravinProtocolHandledKey";
// 這個(gè)方法是注冊后,NSURLProtocol就會(huì)通過這個(gè)方法確定參數(shù)request是否需要被處理
// return : YES 需要經(jīng)過這個(gè)NSURLProtocol"協(xié)議" 的處理, NO 這個(gè) 協(xié)議request不需要遵守這個(gè)NSURLProtocol"協(xié)議"
// 這個(gè)方法的左右 : 1, 篩選Request是否需要遵守這個(gè)NSURLRequest , 2, 處理http: , https等URL
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只處理http和https請求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
{
//看看是否已經(jīng)處理過了,防止無限循環(huán)
if ([NSURLProtocol propertyForKey:TravinProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;
}
return NO;
}
// 這個(gè)方法就是返回request,當(dāng)然這里可以處理的需求有 :
// 1,規(guī)范化請求頭的信息 2, 處理DNS劫持,重定向App中所有的請求指向等
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
// 這個(gè)方法主要用來判斷兩個(gè)請求是否是同一個(gè)請求,
// 如果是,則可以使用緩存數(shù)據(jù),通常只需要調(diào)用父類的實(shí)現(xiàn)即可,默認(rèn)為YES,而且一般不在這里做事情
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
// 開始初始化一個(gè)NSURLProtocol抽象對象, 包含請求, cachedResponse , 和建立client
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
if (self) {
}
return self;
}
// 需要在該方法中發(fā)起一個(gè)請求,對于NSURLConnection來說,就是創(chuàng)建一個(gè)NSURLConnection,對于NSURLSession,就是發(fā)起一個(gè)NSURLSessionTask
// 另外一點(diǎn)就是這個(gè)方法之后,會(huì)回調(diào)<NSURLProtocolClient>協(xié)議中的方法,
- (void)startLoading
{
NSString *cacheKey = self.request.URL.absoluteString;
// 1.根據(jù)URL作為KEY,利用PRCachedURLResponse創(chuàng)建緩存
TravinCachedURLResponse *cachedResponse = [[TravinObjectCache sharedCache] objectForKey:cacheKey]; //根據(jù)請求的URL,獲取緩存,
if (cachedResponse && cachedResponse.response && cachedResponse.data) { // 如果有緩存
NSURLResponse *response = cachedResponse.response;
NSData *data = cachedResponse.data;
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
return;
}
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[newRequest setTimeoutInterval:15]; // 設(shè)置超時(shí)請求
// 給我們處理過的請求設(shè)置一個(gè)標(biāo)識(shí)符, 防止無限循環(huán),
[NSURLProtocol setProperty:@YES forKey:TravinProtocolHandledKey inRequest:newRequest];
// 1.根據(jù)URL作為KEY,利用PRCachedURLResponse創(chuàng)建緩存,如果沒,則創(chuàng)建一個(gè)NSURLConnection,將處理的request與這個(gè)connection鉤起來,同時(shí)實(shí)現(xiàn)NSConnectionDataDelegate的回調(diào)
self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self]; // 創(chuàng)建connection
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//標(biāo)示改request已經(jīng)處理過了,防止無限循環(huán)
[NSURLProtocol setProperty:@YES forKey:TravinProtocolHandledKey inRequest:mutableReqeust];
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
// 這個(gè)方法是和start是對應(yīng)的 一般在這個(gè)方法中,斷開Connection
// 另外一點(diǎn)就是當(dāng)NSURLProtocolClient的協(xié)議方法都回調(diào)完畢后,就會(huì)開始執(zhí)行這個(gè)方法了
- (void)stopLoading
{
// 斷開連接
[self.connection cancel];
}
使用中的一些坑##
在開發(fā)過程中,遇到的一些問題總結(jié)一下
如果注冊了兩個(gè)NSURLProtocol,執(zhí)行順序是怎樣?###
Protocols的遍歷是反向的,也就是最后注冊的Protocol會(huì)被優(yōu)先判斷。
如下圖, 先注冊AAAA,再注冊BBBB的話優(yōu)先判斷的是BBBB,

系列的其他整理
[IOS混合編程 - UIWebView 與 WKWebView . 基本使用 (一)][1]
[IOS混合編程 - Http for IOS (二)][2]
[IOS混合編程 - NSURLProtocol 的使用 (三)][3]

[1]: http://www.itdecent.cn/p/b3e7fa514ab7
[2]: http://www.itdecent.cn/p/a6830a9287d6
[3]: http://www.itdecent.cn/p/ec5d6c204e17