iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol

一、前言

NSURLProtocol是iOS中URL Loading System的一部分。如果開發(fā)者自定義的一個NSURLProtocol并且注冊到app中,那么在這個自定義的NSURLProtocol中我們可以攔截UIWebView,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請求,然后做到自定義的response返回。非常強大。

二、NSURLProtocol的使用流程

2.1、在AppDelegate中注冊自定義的NSURLProtocol。

比如我這邊自定義的NSURLProtocol叫做YXNSURLProtocol。

@interface YXNSURLProtocol : NSURLProtocol
@end

在系統(tǒng)加載的時候,把自定義的YXNSURLProtocol注冊到URL加載系統(tǒng)中,這樣 所有的URL請求都有機會進(jìn)入我們自定義的YXNSURLProtocol進(jìn)行攔截處理。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[YXNSURLProtocol class]];
}

加載完成后,當(dāng)產(chǎn)生URL請求的同時,會依次進(jìn)入NSURLProtocol的以下相關(guān)方法進(jìn)行處理,下面我們依次來講一下每一個方法的作用。

2.2、NSURLProtocol中的幾個方法

2.2.1、是否進(jìn)入自定義的NSURLProtocol加載器

+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    BOOL intercept = YES;
    NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString);
    if (intercept) {
        
    }
    return intercept;
}

如果返回YES則進(jìn)入該自定義加載器進(jìn)行處理,如果返回NO則不進(jìn)入該自定義選擇器,使用系統(tǒng)默認(rèn)行為進(jìn)行處理。

如果這一步驟返回YES。則會進(jìn)入2.3的方法中。

2.2.2、重新設(shè)置NSURLRequest的信息

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

在這個方法中,我們可以重新設(shè)置或者修改request的信息。比如請求重定向或者添加頭部信息等等。如果沒有特殊需求,直接返回request就可以了。但是因為這個方法在會在一次請求中被調(diào)用多次(暫時我也不知道什么原因為什么需要回調(diào)多洗),所以request重定向和添加頭部信息也可以在開始加載中startLoading方法中重新設(shè)置。

2.2.3、這個方法主要是用來判斷兩個request是否相同,如果相同的話可以使用緩存數(shù)據(jù),通常調(diào)用父類的實現(xiàn)即可

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
    return [super requestIsCacheEquivalent:a toRequest:b];
}

這個方法基本不常用。

2.2.4、被攔截的請求開始執(zhí)行的地方

- (void)startLoading{
}

這個函數(shù)使我們重點使用的函數(shù)。

2.2.5、結(jié)束加載URL請求

- (void)stopLoading{
}

2.3、NSURLProtocolClient中的幾個方法

上面的NSURLProtocol定義了一系列加載的流程。而在每一個流程中,我們作為使用者該如何使用URL加載系統(tǒng),則是NSURLProtocolClient中幾個方法該做的事情。

@protocol NSURLProtocolClient <NSObject>

//請求重定向
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;

// 響應(yīng)緩存是否合法
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;

//剛接收到Response信息
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;

//數(shù)據(jù)加載成功
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;

//數(shù)據(jù)完成加載
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;

//數(shù)據(jù)加載失敗
- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;

//為指定的請求啟動驗證
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

//為指定的請求取消驗證
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

@end

三、實現(xiàn)一個地址重定向的Demo

這個Demo實現(xiàn)的功能是在UIWebView中所有跳轉(zhuǎn)到sina首頁的請求,都重定位到sohu首頁。

3.1、第一步,新建一個UIWebView,加載sina首頁

    _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

@interface YXNSURLProtocolTwo : NSURLProtocol

@end

3.3、在AppDelegate中,進(jìn)行注冊

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[YXNSURLProtocolTwo class]];
    return YES;
}

3.4、在canInitWithRequest方法中攔截https://sina.cn/

+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    NSLog(@"canInitWithRequest url-->%@",request.URL.absoluteString);
    
    //看看是否已經(jīng)處理過了,防止無限循環(huán)
    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中進(jìn)行方法重定向

- (void)startLoading{
    
    NSMutableURLRequest * request = [self.request mutableCopy];
    
    // 標(biāo)記當(dāng)前傳入的Request已經(jīng)被攔截處理過,
    //防止在最開始又繼續(xù)攔截處理
    [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;
}

你也可以選擇在<code>+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request</code>替換request。效果都是一樣的。

3.6、因為新建了一個NSURLConnection *connection,所以要實現(xiàn)他的代理方法,如下

- (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];
}

通過以上幾步,我們就可以實現(xiàn)最簡單的url重定向,WebView加載新浪首頁,卻跳轉(zhuǎn)到了搜狐首頁。

四、小結(jié)

通過自定義的NSURLProtocol,我們拿到用戶請求的request之后,我們可以做很多事情。比如:

1、自定義請求和響應(yīng)

2、網(wǎng)絡(luò)的緩存處理(H5離線包 和 網(wǎng)絡(luò)圖片緩存)

3、重定向網(wǎng)絡(luò)請求

4、為測試提供數(shù)據(jù)Mocking功能,在沒有網(wǎng)絡(luò)的情況下使用本地數(shù)據(jù)返回。

5、過濾掉一些非法請求

6、快速進(jìn)行測試環(huán)境的切換

7、攔截圖片加載請求,轉(zhuǎn)為從本地文件加載

8、可以攔截UIWebView,基于系統(tǒng)的NSURLConnection或者NSURLSession進(jìn)行封裝的網(wǎng)絡(luò)請求。目前WKWebView無法被NSURLProtocol攔截。

9、當(dāng)有多個自定義NSURLProtocol注冊到系統(tǒng)中的話,會按照他們注冊的反向順序依次調(diào)用URL加載流程。當(dāng)其中有一個NSURLProtocol攔截到請求的話,后續(xù)的NSURLProtocol就無法攔截到該請求。

五、聯(lián)系方式

新浪微博
github
簡書首頁

歡迎加好友、一起交流。

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

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • iOS網(wǎng)絡(luò)編程讀書筆記 Facade Tester客戶端門面模式的實例(被動版本化) 被動版本化,所以硬編碼URL...
    melouverrr閱讀 1,712評論 3 7
  • 當(dāng)我們寫好anuglar2的程序發(fā)布到服務(wù)器時,使用一級路由是可以正常打開的,如www.soe.xin,但是直接輸...
    索易開發(fā)閱讀 894評論 0 1
  • 大二寫的一篇文章,用這個開始簡書的first blood. 戀愛真是件小事,一件再小不過的事。 昨晚看了《萬有引力...
    ZL_FTD閱讀 260評論 0 0
  • 人生有時候好比修房子,小時候是打地基,年輕一點開始筑墻,中年的時候封瓦成型,老年的時候因為有了堅實的房子也不用太過...
    NukaCoca閱讀 372評論 0 0

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