标签:
启动时注册 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 的部分了:
MyURLProtocolHandledKey是一个常亮字符串,用来标识一个request是否应该被拦截.
+ (BOOL)canInitWithRequest:(NSURLRequest *)request { if ([NSURLProtocol propertyForKey:MyURLProtocolHandledKey inRequest:request]) { NSLog(@"NO"); return NO; } NSLog(@"YES"); return YES; }
如果canInitWithRequest:返回NO, 就表示 当前注册的MyURLPotocol 不能处理这个请求, MyURLPotocol 就会被绕过去,就像不存在一样,原来的request 按照它本来的逻辑去加载;
如果canInitWithRequest: 返回YES,就表示 当前注册的MyURLPotocol 可以处理这个请求,接下来会调用 MyURLPotocol 的 canonicalRequestForRequest:方法,startLoading 方法等。
startLoading 里面是我们拦截下请求后自己的处理逻辑,
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 发送网络请求的情况:
标签:
原文地址:http://my.oschina.net/u/255456/blog/510109