KVOの観測に役立つWWDC 2017 Foundationトークの例に非常に似たものを取得しようとしています。その話とは異なる唯一の違いは、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>から文字列を抽出できませんでした:ファイル/Library/Caches/com.Apple.xbs/Sources/swiftlang/swiftlang-900.0.45.6/src/Swift/stdlib /public/SDK/Foundation/NSObject.Swift、85行目
また、このエラーを参照してください:
エラー:実行が中断されました、理由:EXC_BAD_INSTRUCTION(コード= EXC_I386_INVOP、サブコード= 0x0)。プロセスは中断された時点で終了しました。「スレッドリターン-x」を使用して、式の評価前の状態に戻ります。
他の誰かがこのようなものを機能させることができますか、またはこれは私が報告する必要があるバグですか?
ここでのバグは、コンパイラーがあなたに言うことを許すということです:
@objcMembers class MyController : NSObject {
dynamic var tr: Node
// ...
Node
はstruct
であるため、Obj-Cで直接表すことはできません。ただし、コンパイラーはtr
をdynamic
としてマークすることを許可します–これはが必要です@objc
。 @objcMembers
推論@objc
クラスのメンバーの場合は、Obj-Cで直接表現できるメンバーに対してのみそうですが、tr
はできません。
つまり、コンパイラーはtr
をdynamic
としてマークしてはいけません–私は先に進んで ここにバグを報告しました 、これは 修正されました) およびSwift 5。
tr
が必要です@objc
&dynamic
は、KVOを使用するためのものです。KVOには、Obj-Cランタイムが提供するメソッドのスウィズリングが必要であり、Swiftランタイムはそうではないためです。したがって、ここでKVOは、Node
をclass
にして、NSObject
をObj-Cに公開するためにtr
から継承する必要があります。
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
から継承するNSObject
型であることがわかります)
ただし、指定した例では、実際には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
はまた、KVOなどの多くのキットと実装でコンパイルOC
を混合します。したがって、KVOがOC
にどのように実装されたかを知っておく必要があります。
オブジェクトに対して_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について知っている以上のこと、そしてそれを検索し、私の答えを継続的に更新します。