iOS 10 - 在deinit过程中发生NSKeyValueObservation崩溃

7
我正在使用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的属性,但并没有导致崩溃。

那么,在iOS 10上通过NSKeyValueObservation观察self的属性的正确方法是什么?或者如何注销它?


在Swift 4中,如何删除基于块的KVO观察者? - Larme
事实上,我已经在deinit中尝试了invalidatenil两种方式,但没有成功... - mrfour
3个回答

13

更新于2019/10/16

在iOS 10.3上使用Xcode 11和Swift 5.1仍然存在这个问题,我创建了一个示例项目来测试它,使用了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建议,但没有成功。也许我漏掉了什么?

// Environment: Xcode 11.1, Swift 5.1, iOS 10.3
deinit {
    // Also tried only either one, no luck
    self.observer?.invalidate() 
    self.observer = nil
}

1
我认为下面的代码行不需要: removeObserver(titleObserver, forKeyPath: "title"). - Asif Bilal
没错,这里不需要使用 removeObserver(titleObserver, forKeyPath: "title") - 这会在 iOS 13 上导致崩溃,因为您实际上没有为键路径注册观察者。请删除此行。 - Stacy Smith

5
看起来像是苹果的一个错误 - https://bugs.swift.org/browse/SR-5816 这是因为NSKeyValueObservation对对象持有弱引用,这个弱引用变成nil太早了。
在一些情况下,可以通过使用生命周期方法(比如viewDidDisappear等)找到解决方法。 在其他情况下,您应该使用旧的obj-c API(addObserver / removeObserver / observeValue)以支持iOS 10。

-1
这对我有用:
deinit {
    titleObserver = nil
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接