iOS基础 | WKWebView 使用
之前对于webview的经验只是通过URL展示H5页面,没做过交互。最近有机会接触到js交互,现在把遇到的问题进行整理。
目录:
- 技术选型
- 基础使用
- 交互
- 内存泄漏问题
UIWebView or WKWebView
iOS8之后苹果推荐使用WKWebView替代UIWebView,其优点如下:
- 在性能、稳定性
- WKWebView更多的支持HTML5的特性
- WKWebView更快,占用内存可能只有UIWebView的1/3 ~ 1/4
- WKWebView高达60fps的滚动刷新率和丰富的内置手势
- WKWebView具有Safari相同的JavaScript引擎
- WKWebView增加了加载进度属性
- 将UIWebViewDelegate和UIWebView重构成了14个类与3个协议官方链接
WKWebView 基础使用
// 初始化一个 WKWebViewConfiguration 对象用于配置 WKWebView
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
// 初始化一个 WKUserContentController 对象用于js交互
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
configuration.userContentController = userContentController;
// 使用 WKWebView 的指定初始化方法 initWithFrame: configuration: 初始化 WKWebView
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
[self.view addSubview:self.webView];
// 加载请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:self.url]];
[self.webView loadRequest:request];
以上代码就可以完成基础的网页展示了。需要更多操作还要设置两个代理:
@interface YPHWealthViewController ()<WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler>
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;
// TODO: 代理方法后面总结,未完待续...
交互:js 调用 OC
第一步:给前端同学一行代码,调用OC使用
window.webkit.messageHandlers.accessToken.postMessage(null)
accessToken 即 name;
postMessage() 可以用于传参。
第二步:注册方法
// 这用到了上面提到的 WKUserContentController对象:内容交互控制器
WKUserContentController *userContentController = [WKUserContentController new];
[userContentController addScriptMessageHandler:self name:@"accessToken"];
第三步:遵循协议、实现方法
@interface YPHWealthViewController ()<WKScriptMessageHandler>
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name, message.body);
if ([message.name isEqualToString:@"accessToken"]) {
// 执行操作
}
}
问题:oc如何返回数据
需求:后台希望通过调用accessToken在OC端拿到数据,可是window.webkit.messageHandlers.<name>.postMessage(<messageBody>)
是没有返回值的。
解决办法:OC向js传递数据的3种方法
1.在加载请求时,使用参数
[request setHTTPBody:[token dataUsingEncoding:NSUTF8StringEncoding]];
2.使用WKUserScript
WKUserContentController是用于与JS交互的类,而所注入的JS即WKUserScript对象。使用方法如下:
NSString *sendToken = [NSString stringWithFormat:@"localStorage.setItem(\"accessToken\",'%@');", token];
WKUserScript *script = [[WKUserScript alloc] initWithSource:sendToken injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:script];
accessToken 是js端定义好的方法
3.使用evaluateJavaScript:completionHandler:^
方法
NSString *string = [NSString stringWithFormat:@"accessToken('%@')", token];
[_webView evaluateJavaScript:string completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSLog(@"result=%@, error=%@", result, error);
}];
// 同样,accessToken 是js端定义好的方法
总结:技术方案不同,换个思路,回归本源,从业务角度做出改变
我们最初的问题是不能给js端返回数据,但是可以主动向js端传递数据,于是我用了上面第3个方法。但是问题是,js端调用OC,OC再向js端传递数据,等我数据传递过去,js端早已经执行完了。
回到业务,js端想通过token判断我是否登录,这个token我可以提前给到js端。所以后来改用了第2个方法。下面是我画的流程图:
![](https://img.haomeiwen.com/i1313829/d369ed6de5a55823.png)
经过梳理,js和OC如何交互,何时交互的问题就解决了。
内存泄露问题
[userContentController addScriptMessageHandler:self name:@"accessToken"];
看博客时,很多都提到了self不能释放会导致内存泄漏,这里直接粘出解决代码如下:
@interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler>
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate
{
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end
使用:
YPHWeakScriptMessageDelegate *weakDelegate = [[YPHWeakScriptMessageDelegate alloc] initWithDelegate:self];
[userContentController addScriptMessageHandler:weakDelegate name:@"accessToken"];
还有,在dealloc方法中进行remove操作:
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"loginAction"];
感谢以下简书作者,以及参考链接:
WKWebView学习笔记 by 姜小骚
使用WKWebView替换UIWebView by ch32053
OC中WKWebView与js的交互 by AgoniNemo