iOS 開發之— NSURLProtocol

來源:互聯網
上載者:User

標籤:

最近在項目裡由於電信那邊發生dns發生網域名稱劫持,因此需要手動將URL請求的網域名稱重新導向到指定的IP地址,但是由於請求可能是通過NSURLConnection,NSURLSession或者AFNetworking等方式,因此要想統一進行處理,一開始是想通過Method Swizzling去hook cfnetworking底層方法,後來發現其實有個更好的方法–NSURLProtocol。

 

NSURLProtocol

 

NSURLProtocol能夠讓你去重新定義蘋果的URL載入系統 (URL Loading System)的行為,URL Loading System裡有許多類用於處理URL請求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,當URL Loading System使用NSURLRequest去擷取資源的時候,它會建立一個NSURLProtocol子類的執行個體,你不應該直接執行個體化一個NSURLProtocol,NSURLProtocol看起來像是一個協議,但其實這是一個類,而且必須使用該類的子類,並且需要被註冊。

 

使用情境

 

不管你是通過UIWebView, NSURLConnection 或者第三方庫 (AFNetworking, MKNetworkKit等),他們都是基於NSURLConnection或者 NSURLSession實現的,因此你可以通過NSURLProtocol做自訂的操作。

 

  • 重新導向網路請求

  • 忽略網路請求,使用本機快取

  • 自訂網路請求的返回結果

  • 一些全域的網路請求設定

 

攔截網路請求

 

子類化NSURLProtocol並註冊

 

@interface CustomURLProtocol : NSURLProtocol

@end

 

然後在application:didFinishLaunchingWithOptions:方法中註冊該CustomURLProtocol,一旦註冊完畢後,它就有機會來處理所有交付給URL Loading system的網路請求。

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //註冊protocol

    [NSURLProtocol registerClass:[CustomURLProtocol class]];

    return YES;

}

 

實現CustomURLProtocol

 

註冊好了之後,現在可以開始實現NSURLProtocol的一些方法:

 

  • +canInitWithRequest:

    這個方法主要是說明你是否打算處理對應的request,如果不打算處理,返回NO,URL Loading System會使用系統預設的行為去處理;如果打算處理,返回YES,然後你就需要處理該請求的所有東西,包括擷取請求資料並返回給 URL Loading System。網路資料可以簡單的通過NSURLConnection去擷取,而且每個NSURLProtocol對象都有一個NSURLProtocolClient執行個體,可以通過該client將擷取到的資料返回給URL Loading System。

    這裡有個需要注意的地方,想象一下,當你去載入一個URL資源的時候,URL Loading System會詢問CustomURLProtocol是否能處理該請求,你返回YES,然後URL Loading System會建立一個CustomURLProtocol執行個體然後調用NSURLConnection去擷取資料,然而這也會調用URL Loading System,而你在+canInitWithRequest:中又總是返回YES,這樣URL Loading System又會建立一個CustomURLProtocol執行個體導致無限迴圈。我們應該保證每個request只被處理一次,可以通過+setProperty:forKey:inRequest:標示那些已經處理過的request,然後在+canInitWithRequest:中查詢該request是否已經處理過了,如果是則返回NO。

 

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

  //只處理http和https請求

    NSString *scheme = [[request URL] scheme];

    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||

     [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))

    {

        //看看是否已經處理過了,防止無限迴圈

        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {

            return NO;

        }

 

        return YES;

    }

    return NO;

}

 

  • +canonicalRequestForRequest:

    通常該方法你可以簡單的直接返回request,但也可以在這裡修改request,比如添加header,修改host等,並返回一個新的request,這是一個抽象方法,子類必須實現。

 

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {

    NSMutableURLRequest *mutableReqeust = [request mutableCopy];

    mutableReqeust = [self redirectHostInRequset:mutableReqeust];

    return mutableReqeust;

}

 

+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request

{

    if ([request.URL host].length == 0) {

        return request;

    }

 

    NSString *originUrlString = [request.URL absoluteString];

    NSString *originHostString = [request.URL host];

    NSRange hostRange = [originUrlString rangeOfString:originHostString];

    if (hostRange.location == NSNotFound) {

        return request;

    }

    //定向到bing搜尋首頁

    NSString *ip = @"cn.bing.com";

 

    // 替換網域名稱

    NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];

    NSURL *url = [NSURL URLWithString:urlString];

    request.URL = url;

 

    return request;

}

 

  • +requestIsCacheEquivalent:toRequest:

    主要判斷兩個request是否相同,如果相同的話可以使用快取資料,通常只需要調用父類的實現。

 

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b

{

    return [super requestIsCacheEquivalent:a toRequest:b];

}

 

  • -startLoading -stopLoading

    這兩個方法主要是開始和取消相應的request,而且需要標示那些已經處理過的request。

 

- (void)startLoading

{

    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];

    //標示改request已經處理過了,防止無限迴圈

    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];

    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];

}

 

- (void)stopLoading

{

    [self.connection cancel];

}

 

  • NSURLConnectionDataDelegate方法

    在處理網路請求的時候會調用到該代理方法,我們需要將收到的訊息通過client返回給URL Loading System。

 

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

}

 

現在你已經可以截取request並做你想做的事了,這裡有個demo:https://github.com/FreeMind-LJ/NSURLProtocolExample

可以參考一下,截取request並重新定向到新的地址,具體dns解析方法可以參看DNS解析(http://www.jianshu.com/p/d945454e3abc) ,如有不對,歡迎指正,哈~

 

iOS 開發之— NSURLProtocol

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.