iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看225 回复回复1 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 225|回复: 1
收起左侧

iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol

[复制链接]
jswift 发表于 2016-10-28 09:42:18 | 显示全部楼层 |阅读模式
快来登录
获取最新的苹果动态资讯
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
本帖最后由 jswift 于 2016-10-28 12:01 编辑

请先看《iOS H5容器的一些探究(一):UIWebView和WKWebView的比较和选择》。




1、前言

NSURLProtocol是iOS中URL Loading System的一部分。如果开发者自定义的一个NSURLProtocol并且注册到app中,那么在这个自定义的NSURLProtocol中我们可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求,然后做到自定义的response返回。非常强大。

iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol

iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol - 敏捷大拇指 - iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol





2、NSURLProtocol的使用流程



2.1、在AppDelegate中注册自定义的NSURLProtocol。

比如我这边自定义的NSURLProtocol叫做YXNSURLProtocol。

[Objective-C] 纯文本查看 复制代码
@interface YXNSURLProtocol : NSURLProtocol
@end


在系统加载的时候,把自定义的YXNSURLProtocol注册到URL加载系统中,这样 所有的URL请求都有机会进入我们自定义的YXNSURLProtocol进行拦截处理。

[Objective-C] 纯文本查看 复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[YXNSURLProtocol class]];
}


加载完成后,当产生URL请求的同时,会依次进入NSURLProtocol的以下相关方法进行处理,下面我们依次来讲一下每一个方法的作用。



2.2、NSURLProtocol中的几个方法


2.2.1、是否进入自定义的NSURLProtocol加载器

[Objective-C] 纯文本查看 复制代码
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
    BOOL intercept = YES;
    NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString);
    if (intercept) {

    }
    return intercept;
}


如果返回YES则进入该自定义加载器进行处理,如果返回NO则不进入该自定义选择器,使用系统默认行为进行处理。

如果这一步骤返回YES。则会进入2.3的方法中。


2.2.2、重新设置NSURLRequest的信息

[Objective-C] 纯文本查看 复制代码
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}


在这个方法中,我们可以重新设置或者修改request的信息。比如请求重定向或者添加头部信息等等。如果没有特殊需求,直接返回request就可以了。但是因为这个方法在会在一次请求中被调用多次(暂时我也不知道什么原因为什么需要回调多洗),所以request重定向和添加头部信息也可以在开始加载中startLoading方法中重新设置。


2.2.3、这个方法主要是用来判断两个request是否相同,如果相同的话可以使用缓存数据,通常调用父类的实现即可

[Objective-C] 纯文本查看 复制代码
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
    return [super requestIsCacheEquivalent:a toRequest:b];
}


这个方法基本不常用。


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

[Objective-C] 纯文本查看 复制代码
- (void)startLoading{
}


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


2.2.5、结束加载URL请求

[Objective-C] 纯文本查看 复制代码
- (void)stopLoading{
}




2.3、NSURLProtocolClient中的几个方法

上面的NSURLProtocol定义了一系列加载的流程。而在每一个流程中,我们作为使用者该如何使用URL加载系统,则是NSURLProtocolClient中几个方法该做的事情。

[Objective-C] 纯文本查看 复制代码
@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





3、实现一个地址重定向的Demo

这个Demo实现的功能是在UIWebView中所有跳转到sina首页的请求,都重定位到sohu首页。



3.1、第一步,新建一个UIWebView,加载sina首页

[Objective-C] 纯文本查看 复制代码
    _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];




3.2、自定义一个NSURLProtocol

[Objective-C] 纯文本查看 复制代码
@interface YXNSURLProtocolTwo : NSURLProtocol

@end





3.3、在AppDelegate中,进行注册

[Objective-C] 纯文本查看 复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[YXNSURLProtocolTwo class]];
    return YES;
}




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

[Objective-C] 纯文本查看 复制代码
+ (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;
}




3.5、在startLoading中进行方法重定向

[Objective-C] 纯文本查看 复制代码
- (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。效果都是一样的。



3.6、因为新建了一个NSURLConnection *connection,所以要实现他的代理方法,如下

[Objective-C] 纯文本查看 复制代码
- (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加载新浪首页,却跳转到了搜狐首页。




4、小结

通过自定义的NSURLProtocol,我们拿到用户请求的request之后,我们可以做很多事情。比如:

1、自定义请求和响应

2、网络的缓存处理(H5离线包 和 网络图片缓存)

3、重定向网络请求

4、为测试提供数据Mocking功能,在没有网络的情况下使用本地数据返回。

5、过滤掉一些非法请求

6、快速进行测试环境的切换

7、拦截图片加载请求,转为从本地文件加载

8、可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求。目前WKWebView无法被NSURLProtocol拦截。

9、当有多个自定义NSURLProtocol注册到系统中的话,会按照他们注册的反向顺序依次调用URL加载流程。当其中有一个NSURLProtocol拦截到请求的话,后续的NSURLProtocol就无法拦截到该请求。




5、联系方式

新浪微博
github


欢迎加好友、一起交流。




作者:景铭巴巴

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

本帖被以下淘专辑推荐:

firefighter 发表于 2016-11-3 10:05:28 | 显示全部楼层
Mark。。。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表