【问题标题】:Memory leak when using WKScriptMessageHandler使用 WKScriptMessageHandler 时的内存泄漏
【发布时间】:2015-09-14 15:26:09
【问题描述】:

不确定我是否在WebKit 中遇到了错误,或者我做错了什么,但我不知道如何使用WKScriptMessageHandler 而不导致WKScriptMessage.body 中包含的任何值泄漏。

我能够组建一个最小的 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>

当我运行项目然后在 Instruments 中打开内存调试器时,我看到以下泄漏:

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

整个项目可以downloaded here

知道发生了什么吗?

【问题讨论】:

    标签: ios macos memory-leaks webkit javascriptcore


    【解决方案1】:

    我在 iOS 9 SDK 上遇到了同样的问题。

    我注意到userContentController.addScriptMessageHandler(self, name: "handler") 将保留处理程序的引用。为防止泄漏,只需在不再需要时删除消息处理程序即可。例如当您关闭所述控制器时,调用将调用removeScriptMessageHandlerForName() 的清理方法。

    您可以考虑将addScriptMessageHandler() 移动到viewWillAppear 并在viewWillDisappear 中添加相应的removeScriptMessageHandlerForName() 调用。

    【讨论】:

    • 最初的问题是关于作为被泄露消息正文传递的值。这个答案强调了WKUserContentController 保留已注册的处理程序直到它们被删除的事实,这很重要,但与消息正文的泄露无关。这是一个很好的答案,但它是对另一个问题的答案。
    【解决方案2】:

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

    您可以通过将-dealloc 添加到WKScriptMessage 来解决此问题,以补偿过度保留。它可能看起来像这样:

    //
    //  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,它应该会为您处理泄漏问题。

    【讨论】:

    • 太棒了!非常感谢!
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      要修复保留周期,您可以使用下一个基于 NSProxy 的通用解决方案来处理任何协议:

      @interface WeakProxy: NSProxy
      
      @property (nonatomic, weak) id object;
      
      @end
      
      @implementation WeakProxy
      
      + (instancetype)weakProxy:(id)object {
          return [[WeakProxy alloc] initWithObject:object];
      }
      
      - (instancetype)initWithObject:(id)object {
          self.object = object;
          return self;
      }
      
      - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
          return [self.object methodSignatureForSelector:selector];
      }
      
      - (void)forwardInvocation:(NSInvocation *)invocation {
          [invocation invokeWithTarget:self.object];
      }
      
      @end
      

      您可以在代码中的某处编写:

      let proxy = (id<WKScriptMessageHandler>)[WeakProxy weakProxy:self];
      [configuration.userContentController addScriptMessageHandler:proxy name:KLoginResponseHandler];
      

      【讨论】:

        猜你喜欢
        • 2021-07-06
        • 2013-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-17
        相关资源
        最近更新 更多