前言
NSURLProtocol
是iOS中URL Loading System的一部分。如果开发者自定义的一个NSURLProtocol并且注册到app中,那么在这个自定义的NSURLProtocol中我们可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求,然后做到自定义的response返回。非常强大。
NSURLProtocol的使用流程
在AppDelegate中注册自定义的NSURLProtocol。
比如我这边自定义的NSURLProtocol
叫做YXNSURLProtocol
。
1 2
| @interface YXNSURLProtocol : NSURLProtocol @end
|
在系统加载的时候,把自定义的YXNSURLProtocol注册到URL加载系统中,这样 所有的URL请求都有机会进入我们自定义的YXNSURLProtocol进行拦截处理。
1 2 3
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[YXNSURLProtocol class]]; }
|
加载完成后,当产生URL请求的同时,会依次进入NSURLProtocol
的以下相关方法进行处理,下面我们依次来讲一下每一个方法的作用。
NSURLProtocol中的几个方法
是否进入自定义的NSURLProtocol加载器
1 2 3 4 5 6 7 8
| + (BOOL)canInitWithRequest:(NSURLRequest *)request{ BOOL intercept = YES; NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString); if (intercept) { } return intercept; }
|
如果返回YES则进入该自定义加载器进行处理,如果返回NO则不进入该自定义选择器,使用系统默认行为进行处理。
如果这一步骤返回YES。则会进入NSURLProtocolClient中的几个方法的方法中。
重新设置NSURLRequest的信息
1 2 3
| + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; }
|
在这个方法中,我们可以重新设置或者修改request的信息。比如请求重定向或者添加头部信息等等。如果没有特殊需求,直接返回request就可以了。但是因为这个方法在会在一次请求中被调用多次(暂时我也不知道什么原因为什么需要回调多洗),所以request重定向和添加头部信息也可以在开始加载中startLoading方法中重新设置。
是否父类的实现
这个方法主要是用来判断两个request是否相同,如果相同的话可以使用缓存数据,通常调用父类的实现即可
1 2 3
| + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{ return [super requestIsCacheEquivalent:a toRequest:b]; }
|
这个方法基本不常用。
被拦截的请求开始执行的地方
这个函数使我们重点使用的函数。
结束加载URL请求
上面的NSURLProtocol定义了一系列加载的流程。而在每一个流程中,我们作为使用者该如何使用URL加载系统,则是NSURLProtocolClient中几个方法该做的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @protocol NSURLProtocolClient <NSObject> //请求重定向 - (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse; // 响应缓存是否合法 - (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse; //刚接收到Response信息 - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy; //数据加载成功 - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data; //数据完成加载 - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol; //数据加载失败 - (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error; //为指定的请求启动验证 - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; //为指定的请求取消验证 - (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; @end
|
实现一个地址重定向的Demo
这个Demo实现的功能是在UIWebView中所有跳转到sina首页的请求,都重定位到sohu首页。
第一步,新建一个UIWebView,加载sina首页
1 2 3 4 5 6
| _webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; _webView.delegate = self; [self.view addSubview:_webView]; NSURL *url = [[NSURL alloc] initWithString:@"https://sina.cn"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [_webView loadRequest:request];
|
自定义一个NSURLProtocol
1 2 3
| @interface YXNSURLProtocolTwo : NSURLProtocol @end
|
在AppDelegate中,进行注册
1 2 3 4
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[YXNSURLProtocolTwo class]]; return YES; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| + (BOOL)canInitWithRequest:(NSURLRequest *)request{ NSLog(@"canInitWithRequest url-->%@",request.URL.absoluteString); //看看是否已经处理过了,防止无限循环 if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) { return NO; } NSString *urlString = request.URL.absoluteString; if([urlString isEqualToString:@"https://sina.cn/"]){ return YES; } return NO; }
|
在startLoading中进行方法重定向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (void)startLoading{ NSMutableURLRequest * request = [self.request mutableCopy]; // 标记当前传入的Request已经被拦截处理过, //防止在最开始又继续拦截处理 [NSURLProtocol setProperty:@(YES) forKey:URLProtocolHandledKey inRequest:request]; self.connection = [NSURLConnection connectionWithRequest:[self changeSinaToSohu:request] delegate:self]; } //把所用url中包括sina的url重定向到sohu - (NSMutableURLRequest *)changeSinaToSohu:(NSMutableURLRequest *)request{ NSString *urlString = request.URL.absoluteString; if ([urlString isEqualToString:@"https://sina.cn/"]) { urlString = @"http://m.sohu.com/"; request.URL = [NSURL URLWithString:urlString]; } return request; }
|
你也可以选择在+ (NSURLRequest )canonicalRequestForRequest:(NSURLRequest )request替换request。效果都是一样的。
实现他的代理方法
因为新建了一个NSURLConnection *connection,所以要实现他的代理方法,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| - (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]; }
|
通过以上几步,我们就可以实现最简单的url重定向,WebView加载新浪首页,却跳转到了搜狐首页。
小结
通过自定义的NSURLProtocol,我们拿到用户请求的request之后,我们可以做很多事情。比如:
- 自定义请求和响应
- 网络的缓存处理(H5离线包 和 网络图片缓存)
- 重定向网络请求
- 为测试提供数据Mocking功能,在没有网络的情况下使用本地数据返回。
- 过滤掉一些非法请求
- 快速进行测试环境的切换
- 拦截图片加载请求,转为从本地文件加载
- 可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求。目前WKWebView无法被NSURLProtocol拦截。
- 当有多个自定义NSURLProtocol注册到系统中的话,会按照他们注册的反向顺序依次调用URL加载流程。当其中有一个NSURLProtocol拦截到请求的话,后续的NSURLProtocol就无法拦截到该请求。
原文链接