はじめに
こんにちはsuzukiです。今回はwebviewのWKUserContentControllerで起きた循環参照について、備忘もかねて触れていこうと思います。
先に結論から言うとaddしたらremoveしましょうです。
循環参照が起きて実害が起きるまでなかなか気付かないのですが、webView関係は実害が起こりやすい+起こるような実装をしがちな印象があります。
循環参照が起きた原因
WKUserContentControllerを利用してweb側とアプリ側でやりとりをすることができます。
例えばwebで設定したアラートをアプリでダイアログ表示などするとか、web側のイベントを検知し、アプリ側でハンドリングしUIAlertControllerを使うのようなことが可能です。
実装
WKUserContentControllerのfunc add(WKScriptMessageHandler, name: String)でjsからメッセージが送られてきた場合のハンドリング先を設定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class ViewController: UIViewController, WKScriptMessageHandler { private var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() // Initialize the webview and add self as a script message handler. self.webView = WKWebView(frame: self.view.frame) // [START add_handler] self.webView.configuration.userContentController.add(self, name: "alertAction") // [END add_handler] self.view.addSubview(self.webView) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let request = URLRequest(url: URL(string: "https://www.google.com")!) self.webView.load(request) } // [START handle_messages] func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { print(message.body) print(message.name) } // [END handle_messages] } |
上記のコードでは”alertAction”と言う名前でwebからメッセージを受け取ったら、下記の関数がよばれます。
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
またweb側ではwebkit.messageHandlers.alertAction.postMessage()で上記のメッセージを呼ぶことが可能です。
実装の問題の箇所
今回のコードは設定した内容は問題なく動くのですが、func add(WKScriptMessageHandler, name: String)でselfを渡してしまっているので、循環参照が起きてしまいます。
1 2 3 | deinit { print("deinit") } |
上記のようにdeinitを記述してもよばれることはありません。
画面を閉じる処理を読んでもメモリリークが発生しインスタンスの破棄が行われなくなってしまいます。
修正方法
結論でも言いましたが、addした内容はremoveしましょう。func removeScriptMessageHandler(forName name: String)を呼ぶことで設定を削除できます。
1 2 3 4 | override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) webView.configuration.userContentController.removeScriptMessageHandler(forName: "scripthandler") } |
さいごに
WebView関連では循環参照による不具合がよく起きがちです。しかも検知するのが、アプリではなくweb側だったりするので、調査に苦労します。問題を未然に防ぐためにも変なWebViewのインスタンスは残さないようにしていきましょう。