Memory leak when using WKScriptMessageHandler

Not sure if I hit an error in WebKit or I am doing something terribly wrong, but I cannot figure out how to use WKScriptMessageHandler without causing a leak due to the value contained in WKScriptMessage.body .

I managed to put together a minimal Mac project to isolate the problem, but to no avail.

In the main view controller:

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

And then in the index.html file:

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

When I run the project, open the memory debugger in the Tools application, I see the following leak:

leak

If I add a button that reloads the request, and does it several dozen times, the amount of application memory continues to grow and drops after a certain threshold. This may take some time before crashing in this minimal example, but in my application, where I receive several messages per second, it takes less than 10 seconds to crash.

The whole project can be downloaded here .

Any idea what is going on?

+7
ios memory-leaks webkit macos javascriptcore
source share
3 answers

What you see is a WebKit error: https://bugs.webkit.org/show_bug.cgi?id=136140 . This was fixed in the WebKit trunk a while ago , but it looks like it has not been merged with any WebKit update.

You can get around this by adding -dealloc to WKScriptMessage , which compensates for excessive persistence. It might look something like this:

 // // 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 superclass -dealloc implementation. [super dealloc]; } @end 

Drop it in the Objective-C file in your project, set the compiler flags for this file containing -fno-objc-arc , and it should take care of the leak for you.

+5
source share

I ran into the same issue in the iOS 9 SDK.

I noticed that userContentController.addScriptMessageHandler(self, name: "handler") save the handler link. To prevent leakage, simply remove the message handler when you no longer need it. for example, when you reject the specified controller, call the cleanup method, which will call removeScriptMessageHandlerForName() .

Perhaps you can move addScriptMessageHandler() to viewWillAppear and add the appropriate removeScriptMessageHandlerForName() calls to viewWillDisappear .

+9
source share

You have a hold cycle here. In your code, ViewController saves WKWebView, WKWebView saves WKWebViewConfiguration, WKWebViewConfiguration saves WKUserContentController, and your WKUserContentController saves your ViewController. As in the comment above, you must remove scriptHandler by calling removeScriptMessageHandlerForName before closing your view controller.

+4
source share

All Articles