iOS (三) - NSURLProtocol 的使用

隨說 : 用著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的方法屬性##

Paste_Image.png
// 這個(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的代理方法一起使用


NSURLProtocolClient.png

隨筆 : 其實(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概覽 緩存組件應(yīng)該說是每個(gè)客戶端程序必備的核心組件,試想對于每個(gè)界面的訪問都必須重新請求勢必降低用戶體驗(yàn)。但是如何...
    默默_David閱讀 2,051評論 1 9
  • “業(yè)精于勤,荒于嬉。行成于思,毀于隨?!薄咎啤宽n愈《進(jìn)學(xué)解》 十分鐘后,煲花生黑豆粥的電壓鍋開始滋滋地...
    留子兒閱讀 323評論 0 1
  • 每個(gè)人都有想殺人的時(shí)候。 阿福第一次動(dòng)這個(gè)想法的時(shí)候,被人踩著腦袋壓在地上。 他穿著沒換洗的衣服,上面有大塊的奶茶...
    我是小嘿閱讀 664評論 0 2
  • import glob 獲取指定目錄下的所有圖片 print glob.glob(r"E:/Picture//.j...
    水平閱讀 2,330評論 0 0
  • 親愛的女兒: 每周給你寫一封信,不知道現(xiàn)在在看這些信你是什么感受,估計(jì)新鮮感已過,熱情已減,可能心里還會(huì)嘀...
    陳虹_dd45閱讀 355評論 2 3

友情鏈接更多精彩內(nèi)容