Contents
  1. 1. 前言
  2. 2. NSURLProtocol的使用流程
    1. 2.1. 在AppDelegate中注册自定义的NSURLProtocol。
    2. 2.2. NSURLProtocol中的几个方法
      1. 2.2.1. 是否进入自定义的NSURLProtocol加载器
      2. 2.2.2. 重新设置NSURLRequest的信息
      3. 2.2.3. 是否父类的实现
      4. 2.2.4. 被拦截的请求开始执行的地方
      5. 2.2.5. 结束加载URL请求
    3. 2.3. NSURLProtocolClient中的几个方法
  3. 3. 实现一个地址重定向的Demo
    1. 3.1. 第一步,新建一个UIWebView,加载sina首页
    2. 3.2. 自定义一个NSURLProtocol
    3. 3.3. 在AppDelegate中,进行注册
    4. 3.4. 在canInitWithRequest方法中拦截https://sina.cn/
    5. 3.5. 在startLoading中进行方法重定向
    6. 3.6. 实现他的代理方法
  4. 4. 小结

前言

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];
}

这个方法基本不常用。

被拦截的请求开始执行的地方

1
2
- (void)startLoading{
}

这个函数使我们重点使用的函数。

结束加载URL请求

1
2
- (void)stopLoading{
}

NSURLProtocolClient中的几个方法

上面的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;
}

在canInitWithRequest方法中拦截https://sina.cn/

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之后,我们可以做很多事情。比如:

  1. 自定义请求和响应
  2. 网络的缓存处理(H5离线包 和 网络图片缓存)
  3. 重定向网络请求
  4. 为测试提供数据Mocking功能,在没有网络的情况下使用本地数据返回。
  5. 过滤掉一些非法请求
  6. 快速进行测试环境的切换
  7. 拦截图片加载请求,转为从本地文件加载
  8. 可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求。目前WKWebView无法被NSURLProtocol拦截。
  9. 当有多个自定义NSURLProtocol注册到系统中的话,会按照他们注册的反向顺序依次调用URL加载流程。当其中有一个NSURLProtocol拦截到请求的话,后续的NSURLProtocol就无法拦截到该请求。

原文链接

Contents
  1. 1. 前言
  2. 2. NSURLProtocol的使用流程
    1. 2.1. 在AppDelegate中注册自定义的NSURLProtocol。
    2. 2.2. NSURLProtocol中的几个方法
      1. 2.2.1. 是否进入自定义的NSURLProtocol加载器
      2. 2.2.2. 重新设置NSURLRequest的信息
      3. 2.2.3. 是否父类的实现
      4. 2.2.4. 被拦截的请求开始执行的地方
      5. 2.2.5. 结束加载URL请求
    3. 2.3. NSURLProtocolClient中的几个方法
  3. 3. 实现一个地址重定向的Demo
    1. 3.1. 第一步,新建一个UIWebView,加载sina首页
    2. 3.2. 自定义一个NSURLProtocol
    3. 3.3. 在AppDelegate中,进行注册
    4. 3.4. 在canInitWithRequest方法中拦截https://sina.cn/
    5. 3.5. 在startLoading中进行方法重定向
    6. 3.6. 实现他的代理方法
  4. 4. 小结