我试图得到一些非常类似于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
此错误:
致命错误:无法从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, 第85行
另请参阅此错误:
错误:执行被中断,原因:EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP,subcode = 0x0)。这个过程一直留在了 它被中断的点,使用"线程返回-x"回到 表达评估前的状态。
是否有其他人能够获得这样的工作,或者这是我需要报告的错误?
答案 0 :(得分:17)
这里的错误是编译器让你说:
@objcMembers class MyController : NSObject {
dynamic var tr: Node
// ...
Node
是struct
,因此无法直接在Obj-C中表示。但是,编译器仍允许您将tr
标记为dynamic
- 需要 @objc
。虽然@objcMembers
为类成员推断@objc
,但只有在Obj-C中可直接表示的成员才会这样做,tr
不是。
所以真的,编译器不应该让你将tr
标记为dynamic
- 我继续filed a bug here,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视频,您会看到他们正在观察的属性实际上是继承自class
的{{1}}类型的属性
但是,在您提供的示例中,您并不真正需要KVO - 您可以将NSObject
保留为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
答案 1 :(得分:1)
根据Apple的说法,这是目前的预期行为,因为它取决于Objective-C运行时。这是他们对我的错误报告的回应,它进一步证实了接受的答案海报所说的内容。
答案 2 :(得分:0)
接受的答案是正确的。但我想在Swift中说出我对KVO的了解。
Swift
还在许多Kit和实现中混合编译OC
,例如KVO。所以,你应该知道如何在OC
中实现KVO。
对象addObserver: forKeyPath:
时,OC
运行时创建子类继承对象所属的类,然后在对象更改时重写对象的setter
方法,它会调用setter
和setValue(_ 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的了解,我将不断搜索并更新我的答案。