iOS app秒开H5 NSURLProtocol 拦截方式

拦截并加载本地资源包

NSURLProtocol

公司的项目从 UIWebView 迁移到了 WKWebView。WKWebView性能更优,占用内存更少。

对H5请求进行拦截并加载本地资源,自然想到NSURLProtocol这个神器了。

NSURLProtocol能拦截所有当前app下的网络请求,并且能自定义地进行处理。使用时要创建一个继承NSURLProtocol的子类,不应该直接实例化一个NSURLProtocol。

核心方法

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

判断当前protocol是否要对这个request进行处理(所有的网络请求都会走到这里)。

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

可选方法,对于需要修改请求头的请求在该方法中修改,一般直接返回request即可。

- (void)startLoading

重点是这个方法,拦截请求后在此处理加载本地的资源并返回给webview。

- (void)startLoading
{
    //标示该request已经处理过了,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:self.request];

    NSData *data = [NSData dataWithContentsOfFile:filePath];
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL
                                                            MIMEType:mimeType
                                               expectedContentLength:data.length
                                                    textEncodingName:nil];

    //硬编码 开始嵌入本地资源到web中
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocol:self didLoadData:data];
    [[self client] URLProtocolDidFinishLoading:self];
}

复制代码

- (void)stopLoading

对于拦截的请求,NSURLProtocol对象在停止加载时调用该方法。

注册

[NSURLProtocol registerClass:[NSURLProtocolCustom class]];

其中NSURLProtocolCustom就是继承NSURLProtocol的子类。

但是开发时发现NSURLProtocol核心的几个方法并不执行,难道WKWebview不支持NSURLProtocol?

原来由于网络请求是在非主进程里发起,所以 NSURLProtocol 无法拦截到网络请求。除非使用私有API来实现。使用WKBrowsingContextController和registerSchemeForCustomProtocol。 通过反射的方式拿到了私有的 class/selector。通过把注册把 http 和 https 请求交给 NSURLProtocol 处理。

Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
    // 把 http 和 https 请求交给 NSURLProtocol 处理
    [(id)cls performSelector:sel withObject:@"http"];
    [(id)cls performSelector:sel withObject:@"https"];
}

// 这下 NSURLProtocolCustom 就可以用啦
[NSURLProtocol registerClass:[NSURLProtocolCustom class]];
复制代码

毕竟使用苹果私有api,这是在玩火呀。这篇文章《让 WKWebView 支持 NSURLProtocol》有很好的说明。比如我使用私有api字串拆分,运行时在组合,绕过审核。还可以对字符串加解密等等。。。

实际问题

通过以上处理,可以正常拦截处理,但是又发现拦截不了post请求(拦截到的post请求body体为空),即使在canInitWithRequest:方法中设置对于POST请求的request不处理也不能解决问题。内流。。。

经了解,算是 WebKit 的一个缺陷吧。首先 WebKit 进程是独立于 app 进程之外的,两个进程之间使用消息队列的方式进行进程间通信。比如 app 想使用 WKWebView 加载一个请求,就要把请求的参数打包成一个 Message,然后通过 IPC 把 Message 交给 WebKit 去加载,反过来 WebKit 的请求想传到 app 进程的话(比如 URLProtocol ),也要打包成 Message 走 IPC。出于性能的原因,打包的时候 HTTPBody 和 HTTPBodyStream 这两个字段被丢弃掉了,这个可以参考 WebKit 的源码,这就导致 -[WKWebView loadRequest:] 传出的 HTTPBody 和 NSURLProtocol 传回的 HTTPBody 全都被丢弃掉了。 所以如果通过 NSURLProtocol 注册拦截 http scheme,那么由 WebKit 发起的所有 http POST 请求就全都无效了,这个从原理上就是无解的。

当然网上也出现一些解决方案,但结果都不太理想。同时拦截后对ATS支持不好。再结合又使用了苹果私有API有被拒风险,最终决定弃用NSURLProtocol拦截的方案。

技术支持:
https://juejin.im/post/5c9c664ff265da611624764d
https://www.cnblogs.com/zhuchenglin/p/7528250.html
https://segmentfault.com/p/1210000016879239/read
https://blog.csdn.net/thelittleboy/article/details/84027881
https://www.jianshu.com/p/05616e9a1c7f

你可能感兴趣的:(iOS app秒开H5 NSURLProtocol 拦截方式)