VDwebView 的源码和使用示例 https://github.com/VolientDuan/VDWebView
VDWebView 提供了最全的 API 调用和最方便的 JS 交互方式, 可通过 pod 更新迭代;设计方案为 Protocol 和 Target-Action; 有任何意见或者问题欢迎指出.
- CocoaPods
- pod 'VDWebView', '~> 1.1.0'
基本描述
封装 WebKit 所提供的 WKWebView, 提供熟悉的代理方法(类 UIWebViewDelegate)
更加方便和安全的 JS 与 OC 方法相互调用(后面我会具体说明解决方案)
支持以 target-action 的方式替代 delegate(两者任意选择)
不会出现类似于使用 WKWebView 注册 OC 方法忘记注销导致循环引用无法释放的问题
提供加载进度条的使用, 预估进度值的读取等
cookie 的操作
iOS 和 Android 通用的 JS 与 OC 交互方法(通过请求拦截实现)
为什么使用 WKWebView
WKWebView 是 iOS8 后推出的 WebKit 框架中的控件, 由于 iOS12 后已经弃用 UIWebView 了而且现在的大多数项目只适配到 iOS8
加载速度优于 UIWebView 且解决了加载网页时的内存泄露问题
在和 JS 交互方面提供了桥梁
WKUserContentController
没好处这东西出来干嘛, 所以综上用起来吧
提供了哪些更加熟悉和更加便捷的属性和方法
VDWebViewDelegate
类 UIWebView 代理方法, 处理对象(
- WKNavigationDelegate
- )
- /// 类 UIWebView 代理方法
- - (void)webViewDidStartLoad:(VDWebView *)webView;
- - (void)webViewDidFinishLoad:(VDWebView *)webView;
- - (void)webView:(VDWebView *)webView didFailLoadWithError:(NSError *)error;
- - (BOOL)webView:(VDWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
对 WKUIDelegate 和
WKScriptMessageHandler
代理的合并
- /**
- JS 调原生代理方法(注册了过的方法将全部通过此方法回调)
- */
- - (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message;
- /**
- JS 弹框拦截方法 -- 如果需要自定义弹框建议声明此方法
- */
- - (void)webView:(VDWebView *)webView showAlertWithType:(VDJSAlertType)type title:(NSString *)title content:(NSString *)content completionHandler:(void (^)(id))completionHandler;
新增了哪些属性和方法
详情可见 VDWebViewProtocol
- /// 预估网页加载进度
- @property (nonatomic, readonly) CGFloat estimatedProgress;
- // 是否显示进度条 默认不显示
- @property (nonatomic, assign) BOOL isShowProgressBar;
- /// 进度条
- @property (nonatomic, strong) UIView *progressBar;
- /**
- Web 页面加载完毕后的内容高度(在页面加载完成后获取)
- */
- @property (nonatomic, readonly) CGFloat *contentHeight;
- /// 是否启用 JS 调用原生弹框 默认为 NO 禁止(默认加载弹框在根试图)
- @property (nonatomic, assign) BOOL enableAllAlert;
- @property (nonatomic, assign) BOOL enableAlert;
- @property (nonatomic, assign) BOOL enableConfirm;
- @property (nonatomic, assign) BOOL enablePrompt;
- ///back 层数
- - (NSInteger)countOfHistory;
- - (void)gobackWithStep:(NSInteger)step;
JS 调用 OC 方法的绑定
在使用 WKWebView 时我们需要调用 WKWebView 内 configuration 中的 userContentController 所属类 WKUserContentController 提供的实例方法进行注册, 具体方法如下:
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
对应的注销方法为:
- (void)removeScriptMessageHandlerForName:(NSString *)name;
已知的循环引用问题
在使用 addScriptMessageHandler:name: 方法注册时传入的这个 handler 被循环引用, 如果不调用对应的注销方法就会导致 handler 这个对象无法被释放, 如果你这个 handler 传入是 webView 所在的控制器, 那么你就要在销毁这个控制器前注销掉你注册的方法.
tip: 如何知道控制器有没有被释放, 重写 dealloc(), 没走此方法说明未被释放
VDWebView 是如何解决循环引用问题
简要分析可分为下面三步
使用
VDScripMessageHandler
作为注册的 handler
继承协议
WKScriptMessageHandler
提供 target-action 回调方式
保存注册记录
在 VDWebView 的 dealloc()方法中获取注册记录并注销
这些做的好处在于你在使用 VDWebView 时无需自己去一个个手动注销了(如果你注册的方法多的话那就是噩梦了)
VDWebView 是如何进行方法的注册和回调的
方法的注册
- (void)addScriptMessageHandler:(id)scriptMessageHandler name:(NSString *)name;
JS 调用说明
- // 没效果可使用 try-catch
- Windows.webkit.messageHandlers.#OC 方法名 #.postMessage(# 参数 #)
回调方式分两种: delegate 和 target-action; 两种方式只能存一, 优先 delegate
delegate 方式, 只需在控制器中声明 VDWebViewDelegate 中的方法
- (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message;
target-action 方式
不能声明上述的代理方法
在控制器 (方法注册传入的 scriptMessageHandler) 中声明同名的 OC 方法
为什么要增加 target-action 的方式
target-action: 目标 - 动作模式, 拜 C 语言所赐, 更是灵活很多, 编译期没有任何检查, 都是运行时的绑定
在 VDWebView 中就是通过
NSSelectorFromString()
动态加载方法, 再通过 NSMethodSignature 和 NSInvocation 进行方法的签名和调用
这样就可以充分的体现 JS 调用对应的 OC 方法和一对一更加清晰和方便处理
JS 的调用和注入
可通过两种方式进行 JS 方法的调用, 推荐第一种
WKWebView 的同名方法
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler;
UIWebView 的同名方法(不建议使用这个办法, 因为会在内部等待执行结果)
- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString;
脚本的注入和移除
- /**
- 注入脚本(JS...)
- */
- - (void)addUserScriptWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)mainFrameOnly;
- /**
- 移除所有注入的脚本
- */
- - (void)removeAllUserScripts;
cookie 的处理(待优化)
VDWebViewCookiesProtocol
提供 cookie 的共享
由于 WKWebView 的 cookie是和 NSHTTPCookieStorage 不共享,这就造成使用 WKWebView 打开的 Web 页面无法获取到通过原生请求登录的 cookie, 当然其它解决方案有很多种, 比如
通过 URL 的拼接把登录信息传递过去
通过 JS 方法传值
但是用了VDWebView 就不需要考虑 cookie 的问题了, 因为它已经默认把 cookie 带过去了, 当然你也可以手动去关闭
- /**
- 不同步 NSHTTPCookieStorage 存储的 cookies 默认同步: NO
- 同步 NSHTTPCookieStorage 中的 cookie 到 WKWebView 中, 有可能会污染 WKWebView 中的 cookie 管理
- */
- @property (nonatomic, assign)BOOL httpCookiesDisable;
对 cookie 的操作
- /**
- 设置 cookie
- */
- - (void)setCookieWithKey:(NSString *)key value:(NSString *)value expires:(NSTimeInterval)expires domain:(NSString *)domain;
- - (void)setCookies:(NSString *)cookies;
- /**
- 获取 cookie
- */
- - (NSArray *)getCookies;
iOS 和 Android 通用的 JS 与 OC 交互方法 VDWebViewJSBridge
关于使用方法
你只需要调用 VDWebView 继承的协议 VDWebViewProtocol 所提供的初始化方法 bridgeInitialized 即可
- // 调用初始化 在 webView 的控制器中实现同名方法
- self.webView.delegate = self;
- [self.webView bridgeInitialized];
JS 调用原生
与对应的 JS配套使用, 此方案适用于 iOS 和 Android 对应的 JS 文件如下
VDJSWebBridge.JS 传送门
- // 在 JS 中直接调用 VDJSWebBridge.JS 提供的方法, 详情请查看 JS
- // methodName 为控制器中声明的方法名, params 为 JSON 字符串
- vd_jsBridge(methodName, params)
从 JS 方法触发开始整个调用的过程如下
对 params 进行了 base64 编码
最终的请求 URL 格式为 vdjsbridge://methodName?params=base64(params)
App 拦截请求, 解析 URL 匹配 scheme(vdjsbridge), 获取方法名, base64 解码和 JSON 转对象获取参数值
接受到第三步获取的方法名和参数后通过Target-Action 方法调用对应的方法
OC 调用 JS 方法
- // JS 方法
- function jsMethod(param1, param2, param3) {
- alert("使用 jsBridge 调用 js 方法成功参数为:"+param1+param2+param3)
- return "success";
- }
- // OC 调用 JS 方法
- [self.webView.bridge executeJsMethod:@"jsMethod" params:@[@"1",@"2",@"3"] completionHandler:^(id result, NSError *error) {
- NSLog(@"\njs 方法执行 h 结果回调:%@\n 错误信息:%@",result,error);
- }];
后续版本思考和设计中
cookie 的处理
拦截 webView 内部请求通过自定义 URL 的方式进行 JS 交互
App 和 Web 资源共享问题: 比如图片
来源: https://juejin.im/post/5c92034cf265da60fa39400f