文章目录
  1. 1. 场景
  2. 2. addScriptMessageHandler
    1. 2.0.1. 场景重现
    2. 2.0.2. 解决方案

场景

在iOS上加载web页面,同时又需要在web页面上通过js调用iOS的原生功能,比如第三方登录,支付,设计iOS视图操作等等,这个时候一般都需要用到webView-js互交.本文主要记录我在开发过程中遇到的使用WKWebView与js互交时容易出现的内存泄漏问题.

addScriptMessageHandler

WKWebView-JS互交开发中经常使用下面这个方法:

1
2
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self name:@"ListenerOnClick"];

如果使用这个方法为web添加js调用的话就需要做特别处理,否则会导致内存泄漏.下面是我出现问题的具体情况

场景重现

首先,控制器LSLiveViewController的view负责显示webView,并且webView被LSLiveViewController强引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface LSLiveViewController : UIViewController
@property (nonatomic, strong) WKWebView *webView;
@end
@implementation LSLiveViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupWebView];
}
- (void)setupWebView {
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
configuration.userContentController = controller;
[controller addScriptMessageHandler:self name:@"ListenerOnClick"];
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
//这里省略对webview的其他详细设置
[self.view addSubview:self.webView];
}
-(void)dealloc{
NSLog(@"live controller dealloc");
}
@end

然后一切正常,运行正常.但是我发现一个问题,就是控制器的dealloc从不被调用.经过排查发现每次调用了

[controller addScriptMessageHandler:self name:@”ListenerOnClick”]

之后dealloc就不被调用了.这里应该算是引用循环导致的.如果大家在开发的时候没有观察dealloc的话就很容易忽略了这个问题了.这里WKUserContentController对象的addScriptMessageHandler方法的scriptMessageHandler参数传入了将控制器本身(猜测addScriptMessageHandler将会对scriptMessageHandler参数传入的对象做强引用,这点开发文档没有说明),而控制器又强引用了webView,然后webView又强引用了configuration,configuration又强引用了WKUserContentController对象,所以导致了引用循环,从而导致控制器不被释放的问题.

解决方案

查阅Apple开发文档发现了WKUserContentController的另一个方法
- removeScriptMessageHandlerForName:
改方法用来移除由addScriptMessageHandler加入的ScriptMessageHandler.现在知道解决方法了.我们可以优化一下代码,将上面添加js句柄的代码和移除js句柄代码封装一下.

1
2
3
4
5
6
7
8
-(void)addAllScriptMsgHandle{
WKUserContentController *controller = self.webView.configuration.userContentController;
[controller addScriptMessageHandler:self name:@"ListenerOnClick"];
}
-(void)removeAllScriptMsgHandle{
WKUserContentController *controller = self.webView.configuration.userContentController;
[controller removeScriptMessageHandlerForName:@"ListenerOnClick"];
}

随后在viewDidLoad里面调用addAllScriptMsgHandle完成js句柄添加.然后在你即将离开控制器并且希望它被释放的地方调用removeAllScriptMsgHandle将所以js句柄移除,即可解决问题了.经过测试,dealloc方法成功调用了,说明控制器可以被正确释放了.😄

注意: 如果控制器还不需要被释放,而且webView可能重新显示,则不可以移除js句柄再重新添加相同的句柄,这样会造成webView没法正确调用这些js,造成其他莫名其妙的问题.
文章目录
  1. 1. 场景
  2. 2. addScriptMessageHandler
    1. 2.0.1. 场景重现
    2. 2.0.2. 解决方案