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的对应的关系,  

相关文章
相关标签/搜索