I'm using NSKeyValueObservation to observe properties in a subclass ofWKWebView.
It works well on iOS 11, but crashes on deinit on iOS 10.
*** 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>
)'
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 ?? "")
})
}
}
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?
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With