问题描述
我的视图控制器显示一个 WKWebView.我安装了一个消息处理程序,这是一个很酷的 Web Kit 功能,它允许从网页内部通知我的代码:
My view controller displays a WKWebView. I installed a message handler, a cool Web Kit feature that allows my code to be notified from inside the web page:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let url = // ...
self.wv.loadRequest(NSURLRequest(URL:url))
self.wv.configuration.userContentController.addScriptMessageHandler(
self, name: "dummy")
}
func userContentController(userContentController: WKUserContentController,
didReceiveScriptMessage message: WKScriptMessage) {
// ...
}
到目前为止一切都很好,但现在我发现我的视图控制器正在泄漏 - 当它应该被释放时,它不是:
So far so good, but now I've discovered that my view controller is leaking - when it is supposed to be deallocated, it isn't:
deinit {
println("dealloc") // never called
}
似乎仅仅将自己安装为消息处理程序会导致保留周期,从而导致泄漏!
It appears that merely installing myself as a message handler causes a retain cycle and hence a leak!
推荐答案
像往常一样正确,King Friday.事实证明,WKUserContentController 保留其消息处理程序.这在一定程度上是有道理的,因为如果它的消息处理程序不复存在,它几乎无法向其消息处理程序发送消息.例如,它与 CAAnimation 保留其委托的方式是平行的.
Correct as usual, King Friday. It turns out that the WKUserContentController retains its message handler. This makes a certain amount of sense, since it could hardly send a message to its message handler if its message handler had ceased to exist. It's parallel to the way a CAAnimation retains its delegate, for example.
但是,它也导致了一个retain循环,因为WKUserContentController本身就在泄漏.这本身并没有太大关系(只有16K),但是视图控制器的retain cycle和leakage很糟糕.
However, it also causes a retain cycle, because the WKUserContentController itself is leaking. That doesn't matter much on its own (it's only 16K), but the retain cycle and leak of the view controller are bad.
我的解决方法是在 WKUserContentController 和消息处理程序之间插入一个蹦床对象.Trampoline 对象只有对真实消息处理程序的弱引用,因此没有保留循环.这是蹦床对象:
My workaround is to interpose a trampoline object between the WKUserContentController and the message handler. The trampoline object has only a weak reference to the real message handler, so there's no retain cycle. Here's the trampoline object:
class LeakAvoider : NSObject, WKScriptMessageHandler {
weak var delegate : WKScriptMessageHandler?
init(delegate:WKScriptMessageHandler) {
self.delegate = delegate
super.init()
}
func userContentController(userContentController: WKUserContentController,
didReceiveScriptMessage message: WKScriptMessage) {
self.delegate?.userContentController(
userContentController, didReceiveScriptMessage: message)
}
}
现在当我们安装消息处理程序时,我们安装的是trampoline对象而不是self
:
Now when we install the message handler, we install the trampoline object instead of self
:
self.wv.configuration.userContentController.addScriptMessageHandler(
LeakAvoider(delegate:self), name: "dummy")
它有效!现在调用deinit
,证明没有泄漏.看起来这不应该起作用,因为我们创建了 LeakAvoider 对象并且从未持有对它的引用;但请记住,WKUserContentController 本身会保留它,所以没有问题.
It works! Now deinit
is called, proving that there is no leak. It looks like this shouldn't work, because we created our LeakAvoider object and never held a reference to it; but remember, the WKUserContentController itself is retaining it, so there's no problem.
为了完整起见,现在调用了 deinit
,您可以在那里卸载消息处理程序,尽管我认为这实际上没有必要:
For completeness, now that deinit
is called, you can uninstall the message handler there, though I don't think this is actually necessary:
deinit {
println("dealloc")
self.wv.stopLoading()
self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
}
这篇关于WKWebView 导致我的视图控制器泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!