标签:
启动时注册 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