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

ios memory-leaks webkit ios8 wkwebview

9625 观看

4回复

335532 作者的声誉

我的视图控制器显示WKWebView。我安装了一个消息处理程序,一个很酷的Web Kit功能,允许从网页内部通知我的代码:

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
}

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

作者: matt 的来源 发布者: 2014 年 10 月 15 日

回应 (4)


113

335532 作者的声誉

决定

国王星期五像往常一样正确。事实证明,WKUserContentController 保留了它的消息处理程序。这有一定意义,因为如果消息处理程序不再存在,它很难向消息处理程序发送消息。例如,它与CAAnimation保留其委托的方式并行。

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

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

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

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

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

有用!现在deinit被称为,证明没有泄漏。看起来这应该不起作用,因为我们创建了LeakAvoider对象并且从未对其进行过引用; 但请记住,WKUserContentController本身保留它,所以没有问题。

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

deinit {
    println("dealloc")
    self.wv.stopLoading()
    self.wv.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
}
作者: matt 发布者: 15.10.2014 12:47

21

1089 作者的声誉

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

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

作者: siuying 发布者: 07.09.2015 05:14

17

4087 作者的声誉

由matt发布的解决方案正是我们所需要的。以为我会把它翻译成Objective-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"];
作者: johan 发布者: 27.10.2015 10:26

0

2582 作者的声誉

基本问题:WKUserContentController包含对添加到其中的所有WKScriptMessageHandler的强引用。您必须手动删除它们。

由于这仍然是Swift 4.2和iOS 11的问题,我想建议一个使用处理程序的解决方案,该处理程序与保存UIWebView的视图控制器分开。这样,视图控制器可以正常退出并告诉处理程序也可以清理。

这是我的解决方案:

UIViewController中:

import UIKit
import WebKit

class MyViewController: JavascriptMessageHandlerDelegate {

    private let javascriptMessageHandler = JavascriptMessageHandler()

    private lazy var webView: WKWebView = WKWebView(frame: .zero, configuration: self.javascriptEventHandler.webViewConfiguration)

    override func viewDidLoad() {
        super.viewDidLoad()

        self.javascriptMessageHandler.delegate = self

        // TODO: Add web view to the own view properly

        self.webView.load(URLRequest(url: myUrl))
    }

    deinit {
        self.javascriptEventHandler.cleanUp()
    }
}

// MARK: - JavascriptMessageHandlerDelegate
extension MyViewController {
    func handleHelloWorldEvent() {

    }
}

处理器:

import Foundation
import WebKit

protocol JavascriptMessageHandlerDelegate: class {
    func handleHelloWorld()
}

enum JavascriptEvent: String, CaseIterable {
    case helloWorld
}

class JavascriptMessageHandler: NSObject, WKScriptMessageHandler {

    weak var delegate: JavascriptMessageHandlerDelegate?

    private let contentController = WKUserContentController()

    var webViewConfiguration: WKWebViewConfiguration {
        for eventName in JavascriptEvent.allCases {
            self.contentController.add(self, name: eventName.rawValue)
        }

        let config = WKWebViewConfiguration()
        config.userContentController = self.contentController

        return config
    }

    /// Remove all message handlers manually because the WKUserContentController keeps a strong reference on them
    func cleanUp() {
        for eventName in JavascriptEvent.allCases {
            self.contentController.removeScriptMessageHandler(forName: eventName.rawValue)
        }
    }

    deinit {
        print("Deinitialized")
    }
}

// MARK: - WKScriptMessageHandler
extension JavascriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        // TODO: Handle messages here and call delegate properly
        self.delegate?.handleHelloWorld()
    }
}
作者: Philipp Otto 发布者: 11.01.2019 10:39
32x32