NSURLProtocol

注意區分類方法實例方法,對理解NSProtocol 的原理有幫助。web




啓動時註冊 NSURLProtocol 類的實現類 MyURLProtocol緩存

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    
    [NSURLProtocol registerClass:MyURLProtocol.class];
    
    return YES;
}


UI 界面中,從一個文本框輸入url 發送請求:網絡

#pragma mark - IBAction

- (IBAction)buttonGoClicked:(id)sender {
    
    if ([self.textField isFirstResponder]) {
        [self.textField resignFirstResponder];
    }
    
    [self sendRequest];
}

#pragma mark - UITextFieldDelegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    
    [self sendRequest];
    
    return YES;
}

#pragma mark - Private

- (void) sendRequest {
    
    NSString *text = self.textField.text;
    if (![text isEqualToString:@""]) {
        
        NSURL *url = [NSURL URLWithString:text];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [self.webView loadRequest:request];
        
    }
    
}


以後就是 MyURLPotocol 的部分了:app

MyURLProtocolHandledKey是一個常亮字符串,用來標識一個request是否應該被攔截.ide


首先是canInitWithRequest:方法:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    if ([NSURLProtocol propertyForKey:MyURLProtocolHandledKey inRequest:request]) {
        NSLog(@"NO");
        return NO;
    }
    NSLog(@"YES");
    return YES;
}

若是canInitWithRequest:返回NO, 就表示 當前註冊的MyURLPotocol 不能處理這個請求, MyURLPotocol 就會被繞過去,就像不存在同樣,原來的request 按照它原本的邏輯去加載;url

若是canInitWithRequest: 返回YES,就表示 當前註冊的MyURLPotocol 能夠處理這個請求,接下來會調用 MyURLPotocol 的 canonicalRequestForRequest:方法等,接下來會生成一個MyURLProtocol 的實例,調用實例的startLoading 方法等spa


接下來是關鍵的startLoading 方法:

startLoading 裏面是咱們攔截下請求後本身的處理邏輯, code

1)首先檢測是否有符合當前request的緩存對象(使用CoreData保存),若是有的話就把緩存對象做爲請求的返回,並顯示調用client ( NSURLProtocolClient類型,和 NSURLConnectionDelegate很是類似)喚起 URL Loading System 的回調。注意緩存規則使用了 不容許緩存 , 由於咱們使用已經CoreData進行緩存了。對象

2)若是沒有緩存對象, 則複製當前request ,並設置 MyURLProtocolHandledKey 標誌,告訴 MyURLPotocol 不攔截這個請求( 由於全部的url 請求都會經過 NSURLProtocol的 canInitWithRequest:方法的檢測),最後經過正常的NSURLConnection 或 NSURLSession 發送請求。事件

NSURLConnection 須要一個  NSURLConnectionDelegate 來接收 事件的回調(響應、數據、成功、失敗等事件),這裏MyURLPotocol 實現了 NSURLConnectionDelegate,因此註冊爲 self。

- (void) startLoading {
    NSLog(@"startLoading");
    CachedURLResponse *cachedResponse = [self cachedResponseForCurrentRequest];
    if (cachedResponse) {
        NSData *data = cachedResponse.data;
        NSString *mimeType = cachedResponse.mimeType;
        NSString *encoding = cachedResponse.encoding;
        
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL
                                                            MIMEType:mimeType
                                               expectedContentLength:data.length
                                                    textEncodingName:encoding];
        
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        [self.client URLProtocol:self didLoadData:data];
        [self.client URLProtocolDidFinishLoading:self];
        
    } else {
        
        NSMutableURLRequest *newRequest = [self.request mutableCopy];
        [NSURLProtocol setProperty:@YES forKey:MyURLProtocolHandledKey inRequest:newRequest];
        
        self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];  
    } 
}


NSURLConnectionDelegate 方法的實現:

在這些方法中,仍然要經過 client 對象通知 URL Loading System

在connectiongDidFinishLoading:方法裏,使用CoreData 保存請求返回的數據。

#pragma mark - NSURLConnectionDelegate
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSLog(@"=> connection: didReceiveResponse: ");
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    self.response = response;
    self.mutableData = [[NSMutableData alloc] init];
}

- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    NSLog(@"=> == =====connection: didReceiveData:  =======");
    [self.client URLProtocol:self didLoadData:data];
    [self.mutableData appendData:data];
}

- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
    [self.client URLProtocolDidFinishLoading:self];
    [self saveCachedResponse];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self.client URLProtocol:self didFailWithError:error];
}


      


請求的過程如圖所示, 虛線表示 請求被攔截後發現找不到本地緩存,須要經過 URLConnection 發送網絡請求的狀況:

注:一個request 對應一個 NSURLProtocol 實例, 注意 request 和response的對應的關係,  

相關文章
相關標籤/搜索