Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 973
shannoga Avatar asked Feb 20 '16 21:02

shannoga


3 Answers

Put this inside deinit method of child view controller:

webView.scrollView.delegate = nil
like image 108
Krešimir Prcela Avatar answered Nov 04 '22 08:11

Krešimir Prcela


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.

like image 9
Timur Bernikovich Avatar answered Nov 04 '22 08:11

Timur Bernikovich


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.

like image 3
Arun Ammannaya Avatar answered Nov 04 '22 08:11

Arun Ammannaya