15

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

Community
  • 1
  • 1
shannoga
  • 19,649
  • 20
  • 104
  • 169

4 Answers4

12

Put this inside deinit method of child view controller:

webView.scrollView.delegate = nil
Krešimir Prcela
  • 4,257
  • 33
  • 46
  • Wow. Thanks! We banged our head on this one, can you explain how did find out ? – shannoga Jul 18 '16 at 13:48
  • @shannoga looks like `WKWebView` stores `__unsafe_unretained` pointer to your delegate. Sometimes on web view deallocated not immediate after view controller deallocation and this cause crash when web view tries to notify delegate something. – Timur Bernikovich Feb 08 '17 at 08:21
9

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.

Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58
3

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.

Aruna Mudnoor
  • 4,795
  • 14
  • 16
0

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

dimpiax
  • 12,093
  • 5
  • 62
  • 45