iOS 10 - NSKeyValueObservation 在 deinit 时崩溃
iOS 10 - NSKeyValueObservation crash on deinit
我正在使用 NSKeyValueObservation
观察 WKWebView
的子类中的属性。
它在 iOS 11 上运行良好,但在 iOS 10 上的 deinit
上崩溃。
在控制台上打印错误日志
*** 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 ?? "")
})
}
}
问题
我还发现 contentSizeObserver
,它观察到 scrollView.contentSize
,而不是 self
的属性,没有导致崩溃。
那么,观察 self
在 iOS 10 到 NSKeyValueObservation
上的属性的正确方法是什么?或取消注册?
看起来像苹果虫 – https://bugs.swift.org/browse/SR-5816
This is happening because NSKeyValueObservation holds a weak reference to an object. That weak reference turns to nil too soon.
在某些情况下,可以通过使用生命周期方法(viewDidDissapear 等)找到解决方法。
在其他情况下,您应该使用旧的 obj-c api (addObserver / removeObserver / observeValue) 来支持 iOS 10.
更新于 2019/10/16
这似乎仍然发生在 iOS 10.3 和 Xcode 11 和 Swift 5.1 上,我创建了一个 sample project to test it by using code from SR-5752。
到目前为止我想出的最简单的方法是这样的:
// 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")
}
}
可以注意到我只在 iOS 10 和更低版本上调用 removeObserver(_:forKeyPath)
因为作为 Stacy Smith ,在 iOS 13 上崩溃(可以在示例中轻松重现项目)。
我也试过 Bryan Rodríguez's ,但没有成功。也许我错过了什么?
// Environment: Xcode 11.1, Swift 5.1, iOS 10.3
deinit {
// Also tried only either one, no luck
self.observer?.invalidate()
self.observer = nil
}
这对我有用:
deinit {
titleObserver = nil
}
我正在使用 NSKeyValueObservation
观察 WKWebView
的子类中的属性。
它在 iOS 11 上运行良好,但在 iOS 10 上的 deinit
上崩溃。
在控制台上打印错误日志
*** 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 ?? "")
})
}
}
问题
我还发现 contentSizeObserver
,它观察到 scrollView.contentSize
,而不是 self
的属性,没有导致崩溃。
那么,观察 self
在 iOS 10 到 NSKeyValueObservation
上的属性的正确方法是什么?或取消注册?
看起来像苹果虫 – https://bugs.swift.org/browse/SR-5816
This is happening because NSKeyValueObservation holds a weak reference to an object. That weak reference turns to nil too soon.
在某些情况下,可以通过使用生命周期方法(viewDidDissapear 等)找到解决方法。 在其他情况下,您应该使用旧的 obj-c api (addObserver / removeObserver / observeValue) 来支持 iOS 10.
更新于 2019/10/16
这似乎仍然发生在 iOS 10.3 和 Xcode 11 和 Swift 5.1 上,我创建了一个 sample project to test it by using code from SR-5752。
到目前为止我想出的最简单的方法是这样的:
// 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")
}
}
可以注意到我只在 iOS 10 和更低版本上调用 removeObserver(_:forKeyPath)
因为作为 Stacy Smith
我也试过 Bryan Rodríguez's
// Environment: Xcode 11.1, Swift 5.1, iOS 10.3
deinit {
// Also tried only either one, no luck
self.observer?.invalidate()
self.observer = nil
}
这对我有用:
deinit {
titleObserver = nil
}