首页 > 解决方案 > Swift 4 KVO 块崩溃:观察到的对象在观察者仍在注册时被释放

问题描述

我最近开始使用 iOS 11 作为目标版本来开发我的应用程序,因为这是默认值。由于某些原因,我现在已将版本降低到 9.3。

该应用程序是纯 swift 4,使用新的 KVO 块事物。我修复了我遇到的一些编译时错误safeAreaInsets等等,并且成功构建了应用程序。一个快速的工作。好的。

我尝试在 iPhone 7 iOS 10.3.1 模拟器上运行它,天哪 - 这是火车残骸。我想UITableViewAutomaticDimension这在当时并不是真的。

无论如何,我已经修复了大部分布局问题,但现在我遇到了一些硬崩溃。我在任何地方都使用过这个新的 KVO,当我返回时它会崩溃。我的导航推送 ViewController 是 KVO 侦听它持有的对象内的字段。当我弹出导航时,视图控制器和对象按顺序被释放,应用程序崩溃,给我这个错误:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fdf2e724250 of class MyProject.MyObject was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x60800003fd80> (
<NSKeyValueObservance 0x610000050020: Observer: 0x61000006f140, Key path: isSelected, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x6180000595f0>
)'

据我所知,它表示观察到的对象MyObject在有人观察变量时被释放isSelected

这是观察中MyViewController的观察代码MyObject

var observations:[NSKeyValueObservation] = []
func someFunction() {
    observations.removeAll()
    let myObject = MyObject(/*init*/)
    self.myObject = myObject
    observations.append(myObject.observe(\.isSelected, changeHandler: { [weak self] (object, value) in
        //Do stuff
    }))
}

我的印象是这种新的神奇 KVO 块样式将解决世界和平,但显然这只适用于 iOS 11。

现在,我已经尝试了一些事情,但我无法让它不崩溃。每次都会发生,我不明白为什么。

由于崩溃日志告诉我观察对象在观察对象时被释放,但我也知道观察对象在观察对象之前被释放,我尝试在观察者中这样做:

//In MyViewController
deinit {
    observations.forEach({$0.invalidate()})
    observations.removeAll()
    print("Observers removed")
}

但这无济于事。我也这样做了:

//In MyObject
deinit{
    print("MyObject deinit")
}

当我做这件事时 - 我得到以下输出:

>Observers removed
>MyObject deinit
>WORLD WAR 5 STACK TRACE

我也试过

//In MyViewController
deinit{
    self.myObject.removeObserver(self, forKeyPath: "isSelected")
}

但我得到的输出是Can't remove because it is not registered as an observer. 所以我猜想实际上MyViewController并不是在使用这个新的 KVO 时被附加的观察者。

为什么这些都不起作用?我必须在何时何地删除 < iOS11 中的观察者?

标签: iosswiftkey-value-observing

解决方案


看起来您遇到了我大约一年前报告的错误,但不幸的是很少受到关注:

https://bugs.swift.org/browse/SR-5752

由于这个错误已经有一段时间没有咬我了,我希望它已经在 Swift 覆盖中得到修复,但我只是尝试将我的代码从错误报告复制到一个 iOS 项目中并在 10.3.1 模拟器中运行它,果然,崩溃又回来了。

您可以像这样解决它:

deinit {
    for eachObservation in observations {
        if #available(/*whichever version of iOS fixes this*/) { /* do nothing */ } else {
            self.removeObserver(eachObservation, forKeyPath: #keyPath(/*the key path*/))
        }
        eachObservation.invalidate()
    }

    observations.removeAll()
}

确保仅在受该错误影响的 iOS 版本上执行此操作,否则您将删除已删除的观察,然后可能会崩溃。这不是很有趣吗?


推荐阅读