Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 10 - NSKeyValueObservation crash on deinit

I'm using NSKeyValueObservation to observe properties in a subclass ofWKWebView.

It works well on iOS 11, but crashes on deinit on iOS 10.


Printed Error Log on Console

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x15209e600 of class Rakuemon.WebView was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x170232da0> (
<NSKeyValueObservance 0x170259bf0: Observer: 0x17027d500, Key path: loading, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x170643ba0>
<NSKeyValueObservance 0x170643480: Observer: 0x170c72f80, Key path: estimatedProgress, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x170643330>
<NSKeyValueObservance 0x170642c70: Observer: 0x17086c0c0, Key path: title, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x1706437b0>
)'

Codes

class WebView: WKWebView {

    // MARK: - Properties

    weak var delegate: WebViewDelegate?

    // MARK: - Private properties

    private var contentSizeObserver: NSKeyValueObservation?
    private var loadingObserver: NSKeyValueObservation?
    private var estimatedProgressObserver: NSKeyValueObservation?
    private var titleObserver: NSKeyValueObservation?

    // MARK: - Life cycle

    override init(frame: CGRect, configuration: WKWebViewConfiguration) {
        super.init(frame: frame, configuration: configuration)
        setupObserver()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - Private functions

private extension WebView {
    func setupObserver() {
        contentSizeObserver = scrollView.observe(\.contentSize, options: [.old, .new], changeHandler: { [unowned self] _, change in
            guard let oldSize = change.oldValue, let newSize = change.newValue, oldSize != newSize else { return }
            self.delegate?.webView?(self, didChangeSizeFrom: oldSize, to: newSize)
        })

        loadingObserver = observe(\.isLoading, changeHandler: { [unowned self] _, _ in
            self.delegate?.webViewIsLoading?(self)
        })

        estimatedProgressObserver = observe(\.estimatedProgress, options: [.new], changeHandler: { [unowned self] _, change in
            guard let newValue = change.newValue else { return }
            self.delegate?.webView?(self, didChangeEstimatedProgress: newValue)
        })

        titleObserver = observe(\.title, options: [.new], changeHandler: { [unowned self] _, change in
            guard let title = change.newValue else { return }
            self.delegate?.webView?(self, didChangeTitle: title ?? "")
        })
    }
}

Question

I also found the contentSizeObserver, which observed scrollView.contentSize, not properties of self, didn't caused crash.

So, what is the proper way to observe self's properties on iOS 10 through NSKeyValueObservation? or unregister it?

like image 490
mrfour Avatar asked Apr 27 '18 08:04

mrfour


1 Answers

Updated on 2019/10/16

This seems to still happen on iOS 10.3 with Xcode 11 and Swift 5.1, I created a sample project to test it by using code from SR-5752.

The easiest way I figure out so far is like so:

// Environment: Xcode 11.1, Swift 5.1, iOS 10.3
deinit {
    if #available(iOS 11.0, *) {} else if let observer = observer {
        removeObserver(observer, forKeyPath: "foo")
    }
} 

Can notice that I only call removeObserver(_:forKeyPath) on iOS 10 and lower because as Stacy Smith mentioned, this crash on iOS 13 (can be reproduced easily in the sample project).

I also tried Bryan Rodríguez's suggestion, but without luck. Maybe I missed something? 🤔

// Environment: Xcode 11.1, Swift 5.1, iOS 10.3
deinit {
    // Also tried only either one, no luck
    self.observer?.invalidate() 
    self.observer = nil
}
like image 66
mrfour Avatar answered Sep 23 '22 08:09

mrfour