Swift 4 (BETA 2) KVO 崩溃,基于 WWDC 谈话

Swift 4 (BETA 2) KVO crashing, based upon WWDC talk

我正在尝试获得与 WWDC 2017 基金会演讲中为 KVO 观察工作的示例非常相似的内容。我看到的与那个谈话不同的唯一区别是,我必须调用 super.init(),并且我必须隐式解包 "kvo" 标记。

以下是在操场上使用的:

struct Node  {
    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]
}

let t = Node(title:"hello", leaf:false, children:[:])
let k1 = \Node.leaf
let k2 = \Node.children
t[keyPath: k1] // returns "false" works
t[keyPath: k2] // returns "[:]" works

@objcMembers class MyController : NSObject {
    dynamic var tr: Node
    var kvo : NSKeyValueObservation!
    init(t: Node) {
        tr = t
        super.init()
        kvo = observe(\.tr) { object, change in
            print("\(object)  \(change)")
        }
    }
}


let x = MyController(t: t)
x.tr = Node(title:"f", leaf:false, children:[:])
x

这个错误:

fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath<__lldb_expr_3.MyController, __lldb_expr_3.Node>: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.45.6/src/swift/stdlib/public/SDK/Foundation/NSObject.swift, line 85

另外,看到这个错误:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

有没有其他人能够得到这样的东西,或者这是一个我需要报告的错误?

这里的错误是编译器让你说:

@objcMembers class MyController : NSObject {
    dynamic var tr: Node
    // ...

Node 是一个 struct,所以不能在 Obj-C 中直接表示。但是,编译器仍然允许您将 tr 标记为 dynamic 需要 @objc。虽然 @objcMembers 为 class 的成员推断出 @objc,但它仅对可以在 Obj-C 中直接表示的成员这样做,而 tr 则不能。

所以真的,编译器不应该让你将 tr 标记为 dynamic – 我继续 filed a bug here, which has now been fixed 并准备 Swift 5.

tr 需要 成为 @objc & dynamic 才能在上面使用 KVO,因为 KVO 需要方法调配,这Obj-C 运行时提供,而 Swift 运行时不提供。因此,要在这里使用 KVO,您需要将 Node 设为 class,并从 NSObject 继承,以便将 tr 公开给 Obj-C:

class Node : NSObject {

    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]

    init(title: String, leaf: Bool, children: [String: Node]) {
        self.title = title
        self.leaf = leaf
        self.children = children
    }
}

(如果您再次查看 WWDC 视频,您会看到他们观察到的 属性 实际上是继承自 [=29= 的 class 类型])

但是,在您给出的示例中,您实际上并不需要 KVO – 您可以将 Node 保留为 struct,而使用 属性 观察器:

struct Node  {
    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]
}

class MyController : NSObject {

    var tr: Node {
        didSet {
            print("didChange: \(tr)")
        }
    }

    init(t: Node) {
        tr = t
    }
}
let x = MyController(t: Node(title:"hello", leaf:false, children: [:]))
x.tr = Node(title:"f", leaf: false, children: [:])
// didChange: Node(title: "f", leaf: false, children: [:])

并且因为 Node 是一个值类型,didSet 也会触发对其属性的任何更改:

x.tr.children["foo"] = Node(title: "bar", leaf: false, children: [:])
// didChange: Node(title: "f", leaf: false, children: [
//  "foo": kvc_in_playground.Node(title: "bar", leaf: false, children: [:])
// ])

根据 Apple 的说法,这是目前的预期行为,因为它取决于 Objective-C 运行时。这是他们对我的错误报告的回应,它进一步证实了已接受的答案发布者所说的话。

接受的答案是正确的。但是我想在Swift.

说说我对KVO的了解

Swift 也在许多 Kit 和实现中混合编译 OC,例如 KVO。所以,你应该知道 OC.

中 KVO 是如何实现的

当你addObserver: forKeyPath:一个对象时,OC运行时创建一个子class继承对象所属的class,然后重写setter对象的方法,当对象改变时,它调用settersetValue(_ value: Any?, forKey key: String)来通知改变。

现在,让我们回到 Swift,因此您的 keyPath 应该是 OC 可接受的类型。

class A {  // it's a Swift class but not a OC class inherit from NSObject
   var observation: NSKeyValueObservation?
   @objc dynamic var count: Int = 0  // @objc for OC, dynamic for setter
}

observation = observe(\.count, options: [.new, .old]) { (vc, change) in
   print("new: \(change.newValue), old: \(change.oldValue)")
}  // it's very strange when don't use result, the observe is failure.

以上是我对KVO的了解,我会不断搜索更新我的答案。