Richtig wie immer, King Friday. Es stellt sich heraus, dass der WKUserContentController seinen Nachrichtenhandler beibehält . Dies ist in gewissem Maße sinnvoll, da es kaum eine Nachricht an seinen Nachrichtenhandler senden könnte, wenn sein Nachrichtenhandler nicht mehr existiert. Dies ist parallel dazu, wie eine CAAnimation beispielsweise ihren Delegaten behält.
Es verursacht jedoch auch einen Aufbewahrungszyklus, da der WKUserContentController selbst undicht ist. Das allein macht nicht viel aus (es sind nur 16 KB), aber der Haltezyklus und das Leck des View Controllers sind schlecht.
Meine Problemumgehung besteht darin, ein Trampolinobjekt zwischen dem WKUserContentController und dem Nachrichtenhandler einzufügen. Das Trampolinobjekt hat nur einen schwachen Bezug zum realen Nachrichtenhandler, daher gibt es keinen Aufbewahrungszyklus. Hier ist das Trampolin-Objekt:
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)
}
}
Wenn wir jetzt den Nachrichtenhandler installieren, installieren wir das Trampolinobjekt anstelle von self
:
self.wv.configuration.userContentController.addScriptMessageHandler(
LeakAvoider(delegate:self), name: "dummy")
Es klappt! Jetzt deinit
wird aufgerufen, um zu beweisen, dass es kein Leck gibt. Es sieht so aus, als ob dies nicht funktionieren sollte, da wir unser LeakAvoider-Objekt erstellt haben und nie einen Verweis darauf hatten. Denken Sie jedoch daran, dass der WKUserContentController es selbst beibehält, sodass es kein Problem gibt.
Der Vollständigkeit deinit
halber können Sie den Nachrichtenhandler dort deinstallieren, obwohl ich dies nicht für notwendig halte:
deinit {
println("dealloc")
self.wv.stopLoading()
self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
}