为什么在使用NSManagedObject的自身清除块KVO时会崩溃?

3

我有一个复杂关系的Core Data模型,我用中间的“连接”对象对其进行建模(更多上下文信息请参见此问题:在Core Data中维护复杂的单向关系)。

我不希望使用该模型的代码知道中间连接对象,因此我正在将一个数组属性添加到我的主要托管对象中,以公开连接的对象(我也希望这些对象是可观察的)。

@objc dynamic public internal(set) lazy var hosts: [Point] = {
        let initialHosts = hostConnections.map { $0.superpoint }
        hostsObservation = track(\Matter.hostConnections_!, on: self,
                                 mapping: \HostConnection.superpoint, to: #keyPath(hosts))
        return initialHosts
    }()

以上代码在hosts数组初始化时,针对连接的NSOrderedSet设置了观察。这里使用了一个通用函数,可以在所有连接类型中创建此模式。观察按预期触发。
我会在willTurnIntoFault()函数中使观察失效。
override public func willTurnIntoFault() {
        hostsObservation?.invalidate()
        print("hostsObservation \(hostsObservation) invalidated")
        super.willTurnIntoFault()
    }

但是,当这个对象在 NSKVODeallocate 中被释放时,我遇到了一个崩溃问题。

    hostsObservation Optional(<_NSKeyValueObservation: 0x600000ccd920>) invalidated
    2019-11-04 12:02:06.467620+0000 Frame[27900:5614740] [General] An instance 0x60000300bc30 of class FrameGraph.Subject_Subject_ was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x6000002d6ae0> (
    <NSKeyValueObservance 0x600000ccd950: Observer: 0x600000ccd920, Key path: hostConnections_, Options: <New: NO, Old: NO, Prior: NO> Context: 0x0, Property: 0x600000c78b10>
...
    0   CoreFoundation                      0x00007fff3f590e45 __exceptionPreprocess + 256
    1   libobjc.A.dylib                     0x00007fff6a1e63c6 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff3f590c77 +[NSException raise:format:] + 193
    3   Foundation                          0x00007fff4180f349 NSKVODeallocate + 442
    4   CoreData                            0x00007fff3f060772 -[_PFManagedObjectReferenceQueue _processReferenceQueue:] + 1154

如您从日志中看到的那样,在崩溃之前,我正在使观察失效,并声称它是一个问题。如果我尝试在 willTurnToFault() 中将观察设置为nil,我的应用程序会在那一点上崩溃。
我是Core Data的新手,有谁能帮我理解我在这里缺少了什么?为什么一个观察自己属性的对象会以这种方式崩溃?
编辑1:
我的跟踪函数如下:
internal func track<P: Point, C: Connection>(_ trackedPath: KeyPath<P, NSOrderedSet>,
                                             on point: P,
                                             mapping connectionPath: KeyPath<C, Point>,
                                             to mappedPoints: String)
    -> NSKeyValueObservation
{
    return point.observe(trackedPath) { [unowned point] data, change in

        // ...

    }
}

编辑2:

我将导致崩溃的代码简化为以下内容:

        public class Matter: Point
        {
            override public func awakeFromFetch() {
                super.awakeFromFetch()
                print("\(hosts)") // Initialise lazy member and set observation
            }

            public override func willTurnIntoFault() {
                super.willTurnIntoFault()
                hostsObservation = nil // <-- EXC_BAD_ACCESS 
            }

            @objc dynamic public internal(set) lazy var hosts: [Point] = {
                hostsObservation = observe(\.hostConnections_) { [unowned self] data, change in } // Empty observation closure
                return [] 
            }()

            private var hostsObservation: NSKeyValueObservation?
        }

// Matter+CoreDataProperties.swift - auto generated
    extension Matter {

        @nonobjc public class func fetchRequest() -> NSFetchRequest<Matter> {
            return NSFetchRequest<Matter>(entityName: "Matter")
        }

        @NSManaged public var hostConnections_: NSOrderedSet?

    }

我不明白为什么在 willTurnToFault() 中将 hostsObservation 设置为 nil 会导致崩溃。

我正在使用 Swift 5 和 Xcode 11.1 构建适用于 OSX 10.12 的应用程序。

第三次编辑:

在一个新的简单项目中重现:https://github.com/GilesHammond/KVO-Core-Data-Crash


Observer: 0x600000ccd920的类是什么?NSTreecontroller是否在观察它? - Willeke
@Willeke 0x600000ccd920 是 NSKeyValueObservation,它是由正在被释放的 NSManagedObject 拥有的。问题可能是我使用了 Swift 闭包方法,这种方法在这里是否无效? - Giles
1
我在10.15.1上尝试了你的项目,在我的端上没有崩溃。 - Lucas Derraugh
@Giles 我将部署目标保持不变,所有代码都使用 Xcode 11.2。 - Lucas Derraugh
感谢您的帮助@LucasDerraugh。非常感激。 - Giles
显示剩余2条评论
1个回答

0

关键问题似乎是我的 Core Data 模型在一个模块中。

我已经更新了示例项目,以展示这里的基础错误。

我已经与 Apple 支持人员交谈。看起来存在系统 Bug。当 NSManagedObject 在一个模块中时,Swift 目前无法正确清理 NSKeyValueObservation。

我已通过反馈助手向 Apple 提交了一个问题。


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