iOS基础 | WKWebView 使用

之前对于webview的经验只是通过URL展示H5页面,没做过交互。最近有机会接触到js交互,现在把遇到的问题进行整理。

目录:
  • 技术选型
  • 基础使用
  • 交互
  • 内存泄漏问题

UIWebView or WKWebView

iOS8之后苹果推荐使用WKWebView替代UIWebView,其优点如下:

  1. 在性能、稳定性
  2. WKWebView更多的支持HTML5的特性
  3. WKWebView更快,占用内存可能只有UIWebView的1/3 ~ 1/4
  4. WKWebView高达60fps的滚动刷新率和丰富的内置手势
  5. WKWebView具有Safari相同的JavaScript引擎
  6. WKWebView增加了加载进度属性
  7. 将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 ()
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 ()
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"JS 调用了 %@ 方法,传回参数 %@",message.name, message.body);
    if ([message.name isEqualToString:@"accessToken"]) {
    // 执行操作
    }
}

问题:oc如何返回数据

需求:后台希望通过调用accessToken在OC端拿到数据,可是window.webkit.messageHandlers..postMessage()是没有返回值的。

解决办法: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个方法。下面是我画的流程图:

iOS基础 | WKWebView 使用_第1张图片
未命名文件.png

经过梳理,js和OC如何交互,何时交互的问题就解决了。

内存泄露问题

[userContentController addScriptMessageHandler:self name:@"accessToken"];

看博客时,很多都提到了self不能释放会导致内存泄漏,这里直接粘出解决代码如下:

@interface WeakScriptMessageDelegate : NSObject

@property (nonatomic, weak) id scriptDelegate;

- (instancetype)initWithDelegate:(id)scriptDelegate;

@end

@implementation WeakScriptMessageDelegate

- (instancetype)initWithDelegate:(id)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

你可能感兴趣的:(iOS基础 | WKWebView 使用)