WKWebView导致我的视​​图控制器泄漏

我的视图控制器显示一个WKWebView。 我安装了一个消息处理程序,一个很酷的Web Kitfunction,可以让我的代码从网页内部得到通知:

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) { // ... } 

到目前为止这么好,但现在我发现我的视图控制器正在泄漏 – 当它被解除分配时,它不是:

 deinit { println("dealloc") // never called } 

看起来,仅仅将自己安装为消息处理程序会导致保留周期,从而导致泄漏!

像往常一样正常,周五国王。 事实certificate,WKUserContentController 保留了它的消息处理程序 。 这样做有一定的意义,因为如果消息处理程序不再存在,它几乎不会向其消息处理程序发送消息。 例如,它与CAAnimation保留委托的方式并行。

但是,它也会导致保留周期,因为WKUserContentController本身正在泄漏。 这并不重要(它只有16K),但视图控制器的保留周期和泄漏是不好的。

我的解决方法是在WKUserContentController和消息处理程序之间插入一个蹦床对象。 蹦床对象对真正的消息处理程序只有一个弱引用,所以没有保留周期。 这是蹦床的对象:

 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) } } 

现在当我们安装消息处理程序时,我们安装了蹦床对象而不是self

 self.wv.configuration.userContentController.addScriptMessageHandler( LeakAvoider(delegate:self), name: "dummy") 

有用! 现在叫deinit ,certificate没有泄漏。 看起来这不应该起作用,因为我们创build了我们的LeakAvoider对象,从来没有提及它; 但请记住,WKUserContentController本身正在保留它,所以没有问题。

为了完整deinit ,现在调用了deinit ,可以在那里卸载消息处理程序,但是我不认为这实际上是必须的:

 deinit { println("dealloc") self.wv.stopLoading() self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy") } 

Matt发布的解决scheme正是您所需要的。 以为我会把它翻译成客观的C代码

 @interface WeakScriptMessageDelegate : NSObject<WKScriptMessageHandler> @property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate; - (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate; @end @implementation WeakScriptMessageDelegate - (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate { self = [super init]; if (self) { _scriptDelegate = scriptDelegate; } return self; } - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message]; } @end 

然后就这样使用它:

 WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"name"]; 

泄漏是由userContentController.addScriptMessageHandler(self, name: "handlerName") ,它将保留对消息处理程序self的引用。

为了防止泄漏,只需在不再需要时通过userContentController.removeScriptMessageHandlerForName("handlerName")删除消息处理程序。 如果在viewDidAppear添加addScriptMessageHandler,则最好在viewDidDisappear中将其删除。