2015-06-27 110 views
7

不知道我是否在WebKit中遇到了错误,或者我正在做一些可怕的错误,但我无法弄清楚如何使用WKScriptMessageHandler而不会导致WKScriptMessage.body中包含的任何值泄漏。使用WKScriptMessageHandler时发生内存泄露

我能够把一个最小的Mac项目放在一起,以隔离问题,但无济于事。

在主视图控制器:

class ViewController: NSViewController { 
    var webView: WKWebView? 

    override func viewDidLoad() { 
    super.viewDidLoad() 
    let userContentController = WKUserContentController() 
    userContentController.addScriptMessageHandler(self, name: "handler") 
    let configuration = WKWebViewConfiguration() 
    configuration.userContentController = userContentController 
    webView = WKWebView(frame: CGRectZero, configuration: configuration) 
    view.addSubview(webView!) 

    let path = NSBundle.mainBundle().pathForResource("index", ofType: "html") 
    let url = NSURL(fileURLWithPath: path!)! 
    webView?.loadRequest(NSURLRequest(URL: url)) 
    } 
} 

extension ViewController: WKScriptMessageHandler { 
    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) { 
    print(message.body) 
    } 
} 

然后在index.html文件:

<html> 
    <head></head> 
    <body> 
    <script type="text/javascript"> 
     webkit.messageHandlers.handler.postMessage("Here's a random number for you: " + Math.random() * 10) 
    </script> 
    </body> 
</html> 

当我运行该项目,然后打开仪器的内存调试器,我看到下面的泄漏:

leak

如果我添加了一个重新加载请求的按钮,并且执行了几次这样的操作,则应用程序的内存占用量不断增加,并在达到某个阈值后崩溃。在这个最小的例子中崩溃可能需要一段时间,但在我的应用程序中,我每秒收到几条消息,崩溃的时间不到10秒。

整个项目可以是downloaded here

想知道发生了什么?

回答

5

你看到的是一个WebKit bug:https://bugs.webkit.org/show_bug.cgi?id=136140。它是fixed in WebKit trunk a while ago,但似乎没有合并到任何WebKit更新中。

您可以通过添加一个-deallocWKScriptMessage来解决此问题,该补偿过度保留。它可能是这个样子:

// 
// WKScriptMessage+WKScriptMessageLeakFix.m 
// TestWebkitMessages 
// 
// Created by Mark Rowe on 6/27/15. 
// Copyright © Mark Rowe. 
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions: 
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software. 
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

#import <mach-o/dyld.h> 
#import <objc/runtime.h> 
#import <WebKit/WebKit.h> 

// Work around <https://webkit.org/b/136140> WKScriptMessage leaks its body 

@interface WKScriptMessage (WKScriptMessageLeakFix) 
@end 

@implementation WKScriptMessage (WKScriptMessageLeakFix) 

+ (void)load 
{ 
    // <https://webkit.org/b/136140> was fixed in WebKit trunk prior to the first v601 build being released. 
    // Enable the workaround in WebKit versions < 601. In the unlikely event that the fix is backported, this 
    // version check will need to be updated. 
    int32_t version = NSVersionOfRunTimeLibrary("WebKit"); 
    int32_t majorVersion = version >> 16; 
    if (majorVersion > 600) 
     return; 

    // Add our -dealloc to WKScriptMessage. If -[WKScriptMessage dealloc] already existed 
    // we'd need to swap implementations instead. 
    Method fixedDealloc = class_getInstanceMethod(self, @selector(fixedDealloc)); 
    IMP fixedDeallocIMP = method_getImplementation(fixedDealloc); 
    class_addMethod(self, @selector(dealloc), fixedDeallocIMP, method_getTypeEncoding(fixedDealloc)); 
} 

- (void)fixedDealloc 
{ 
    // Compensate for the over-retain in -[WKScriptMessage _initWithBody:webView:frameInfo:name:]. 
    [self.body release]; 

    // Call our WKScriptMessage's superclass -dealloc implementation. 
    [super dealloc]; 
} 

@end 

放弃这一在你的项目中的Objective-C的文件,设置编译器标志该文件包含-fno-objc-arc,应该照顾泄漏的为您服务。

+0

太棒了!非常感谢! –

9

我在iOS 9 SDK上遇到过同样的问题。我注意到userContentController.addScriptMessageHandler(self, name: "handler")将保持处理程序的引用。为了防止泄漏,只需在不再需要消息处理程序的情况下删除消息处理程序。例如当您解雇所述控制器时,请致电removeScriptMessageHandlerForName()的清理方法。

您可能会考虑将addScriptMessageHandler()移至viewWillAppear并在viewWillDisappear中添加相应的removeScriptMessageHandlerForName()调用。

+1

这应该是被接受的答案 –

+1

原始问题是关于消息正文泄漏时传递的值。这个答案突出了这样一个事实,即'WKUserContentController'保留注册的处理程序,直到它们被移除,这一点很重要,但与消息体的泄露无关。这是一个很好的答案,但它是解决不同问题的答案。 – bdash

4

你在这里有一个保留周期。 在你的代码中,ViewController保留了WKWebView,WKWebView保留了WKWebViewConfiguration,WKWebViewConfiguration保留了WKUserContentController并且你的WKUserContentController保留了你的ViewController。就像上面的注释一样,在关闭视图控制器之前,您必须通过调用removeScriptMessageHandlerForName来移除scriptHandler。