shannoga
shannoga

Reputation: 19869

WKWebView crashes on deinit

I have this hierarchy - UIViewController -> ChildUIViewController -> WKWebView.

I had an issue with the WKWebView message handler that leaked and prevented the child view controller from being released.

After some reading I found a way to fix the retain cycle by using this fix - WKWebView causes my view controller to leak

Now I can see that the child view controller is reaching deinit but right after that the WKWebView is crashing on deinit (No useful log from Xcode).

Any Idea or direction what could be the issue ?

Thanks

UPDATE here is my code - Code Gist

Upvotes: 15

Views: 6451

Answers (4)

Tim Bernikovich
Tim Bernikovich

Reputation: 5945

Don't forget to remove WKWebView's delegates you added:

deinit {
    webView.navigationDelegate = nil
    webView.scrollView.delegate = nil
}

Looks like WKWebView stores __unsafe_unretained pointer to your delegate. Sometimes when web view deallocated not immediate after view controller deallocation. This cause crash when web view tries to notify delegate something.

Upvotes: 9

Krešimir Prcela
Krešimir Prcela

Reputation: 4281

Put this inside deinit method of child view controller:

webView.scrollView.delegate = nil

Upvotes: 12

Aruna Mudnoor
Aruna Mudnoor

Reputation: 4825

I tried with same way as you mentioned. It works perfectly for me. Code which i tried is,

class CustomWKWebView : WKWebView {

    deinit {
        print("CustomWKWebView - dealloc")
    }

}


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

    deinit {
        print("LeakAvoider - dealloc")
    }

}

class ChildViewController: UIViewController , WKScriptMessageHandler{

    var webView = CustomWKWebView()

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(webView)
        webView.frame = self.view.bounds;
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        let url = NSURL(string: "https://appleid.apple.com")
        webView.loadRequest(NSURLRequest(URL:url!))
        webView.configuration.userContentController.addScriptMessageHandler(
            LeakAvoider(delegate: self), name: "dummy")
    }

    func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)
    {

    }

    deinit {
        print("ChaildViewController- dealloc")
        webView.stopLoading()
        webView.configuration.userContentController.removeScriptMessageHandlerForName("dummy")
    }
}


class ViewController: UIViewController {

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    deinit {
        print("ViewController - dealloc")
    }
}

Log after popping ViewController is:

ViewController - dealloc
ChaildViewController- dealloc
LeakAvoider - dealloc
CustomWKWebView - dealloc

UPDATE

Put below lines in your WebViewViewController's viewWillDisappear function.

    webView.navigationDelegate = nil
    webView.scrollView.delegate = nil

I tried by setting these two delegates in my code and it started crashing. Solved by putting above lines in viewWillDisappear of ChildViewController.

Upvotes: 3

dimpiax
dimpiax

Reputation: 12677

Remember, the reason might caused by weak reference to it. I'm sure that you have instantiated local variable of wrapped class with WKWebView.

Upvotes: 0

Related Questions