WKWebView
替换本地资源的原理是通过NSURLProtocol
代理WKWebView
内的所有网络请求, 中间拿到每次加载的URL
, 然后动态替换成本地沙盒内的对应路径下的资源文件.
什么是NSURLProtocol
NSURLProtocol
是URL Loading System的重要组成部分。它听上去像一个协议类, 其实不是, 它是一个抽象类, 我们可以子类化来拦截网络请求。可以拦截的网络请求包括NSURLSession
,NSURLConnection
以及UIWebvIew
。本来之前是不能拦截WKWebView
的, 因为WKWebView
在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView
上直接使用 NSURLProtocol
无法拦截请求。
但是有大神通过阅读webkit的源码, 以及使用反射的方式拿到了WKWebView
用来处理请求的上下文和注册反注册方法: WKBrowsingContextController
和registerSchemeForCustomProtocol
, unregisterSchemeForCustomProtocol
. 然后通过KVC拿到browsingContextController
实例, 把http
和https
请求注册给NSURLProtocol
处理.
NSURLProtocol结合WKWebView的使用
给NSURLProtocol写一个WKWebView的分类
头文件 NSURLProtocol+WKWebVIew.h
1 |
|
NSURLProtocol+WKWebVIew.m
文件
1 |
|
这样我们就可以方便的给WKWebView
进行注册和反注册, 毕竟我们只需要代理WKWebView
内的请求, 所以当我们webview
关闭的时候, 要取消注册.
子类化NSURLProtocol
如上文所说,NSURLProtocol
是一个抽象类。我们要使用它的时候需要创建它的一个子类.
1 | @interface LPURLProtocol : NSURLProtocol |
使用NSURLProtocol
的主要可以分为5个步骤:
注册—>拦截—>转发—>回调—>结束
注册:
在WKWebView
初始化之前:
1 | [NSURLProtocol registerClass:[LPURLProtocol class]]; |
拦截:
然后在我们子类化的LPURLProtocol.m
内加入以下代码:
1 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { |
其中的resourceTypes
是我自定义的方法, 用来获取我需要拦截替换的后缀名数组. 当我们拦截到请求之后, 通过判断当前请求URL的后缀是否包含在此数组内, 包含在内则进行下一步资源替换的操作.以下是内容:
1 | + (NSArray *)resourceTypes{ |
还有一个canonicalRequestForRequest
方法, 用来对request进行处理, 比如修改header信息等.这里我没做操作:
1 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { |
转发:
在canInitWithRequest
拦截成功之后, 会走startLoading
方法, 我们可以在这个方法中做资源的替换操作.
这里提示一下, 我目前的资源替换处理方式是提前在APP启动的时候, 通过接口比对, 下载对应的资源文件到沙盒内, 沙盒内的路径遵循跟服务端URL
的path
路径一致, 这样替换的时候, 直接取URL
的path
, 然后拼接到沙盒Library/cache
后, 就可以直接拿到对应的资源, 然后进行替换.
首先定义一个静态字符, 用来标记我们已经拦截处理过的request, 不然会循环引用:
1 | static NSString* const kURLProtocolMark = @"kURLProtocolMark"; |
以下是startLoading
的实现, 其中[LPGMManager sharedManager].gmUrl
是我的h5请求的域名,你们可以替换成你们自己的,
1 | - (void)startLoading { |
回调:
既是面向切面的编程,就不能影响到原来网络请求的逻辑。所以上一步将网络请求转发出去以后,当收到网络请求的返回,还需要再将返回值返回给原来发送网络请求的地方。
主要需要需要调用到:
1 | [self.client URLProtocol:self didFailWithError:error]; |
这四个方法来回调给原来发送网络请求的地方。
这里假设我们在转发过程中是使用NSURLSession
发送的网络请求,那么在NSURLSession
的回调方法中,我们做相应的处理即可。并且我们也可以对这些返回,进行定制化处理。
1 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { |
结束:
在一个网络请求完全结束以后,NSURLProtocol
回调用到
- (void)stopLoading
在该方法里,我们完成在结束网络请求的操作。
1 | - (void)stopLoading { |
总结:
那么以上我们就针对WKWebView
实现了NSURLProtocol
的代理转发.
参考文章:
NSURLProtocol全攻略
iOS WKWebview实现拦截js,css,html以及图片资源替换为本地资源的两种方式(NSUrlProtocol)