web-dev-qa-db-ja.com

Swift 4(BETA 2)KWクラッシュ、WWDCトークに基づく

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」を使用して、式の評価前の状態に戻ります。

他の誰かがこのようなものを機能させることができますか、またはこれは私が報告する必要があるバグですか?

14
possen

ここでのバグは、コンパイラーがあなたに言うことを許すということです:

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

Nodestructであるため、Obj-Cで直接表すことはできません。ただし、コンパイラーはtrdynamicとしてマークすることを許可します–これはが必要です@objc@objcMembers推論@objcクラスのメンバーの場合は、Obj-Cで直接表現できるメンバーに対してのみそうですが、trはできません。

つまり、コンパイラーはtrdynamicとしてマークしてはいけません–私は先に進んで ここにバグを報告しました 、これは 修正されました) およびSwift 5。

trが必要です@objcdynamicは、KVOを使用するためのものです。KVOには、Obj-Cランタイムが提供するメソッドのスウィズリングが必要であり、Swiftランタイムはそうではないためです。したがって、ここでKVOは、Nodeclassにして、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は必要ありません。Nodestructとして保持し、代わりにプロパティオブザーバーを使用できます。

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: [:])
// ])
20
Hamish

Appleによると、これはObjective-Cランタイムに依存するため、現時点で意図された動作です。これは私のバグレポートに対する彼らの応答であり、承認された回答者の投稿者の発言をさらに裏付けています。

1
possen

受け入れられた答えは正しいです。しかし、私はSwiftでKVOについて知っていることを述べたいと思います。

Swiftはまた、KVOなどの多くのキットと実装でコンパイルOCを混合します。したがって、KVOがOCにどのように実装されたかを知っておく必要があります。

オブジェクトに対して_addObserver: forKeyPath:_を実行すると、OCランタイムはオブジェクトが属するクラスを継承してサブクラスを作成し、オブジェクトが変更されたときにオブジェクトのsetterメソッドを書き換えます。 setterおよびsetValue(_ value: Any?, forKey key: String)を呼び出して、変更を通知します。

それでは、Swiftに戻りましょう。つまり、keyPathOC受け入れられる型である必要があります。

_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について知っている以上のこと、そしてそれを検索し、私の答えを継続的に更新します。

0
yuanjilee