web-dev-qa-db-fra.com

Swift 4 (BETA 2) KVO plante, basé sur la conférence WWDC

J'essaie d'obtenir quelque chose de très similaire à l'exemple de la conférence de la Fondation WWDC 2017 en travaillant pour l'observation du KVO. Les seules différences que je vois qui sont différentes de cet exposé sont, j'ai dû appeler super.init (), et j'ai dû implicitement déballer le jeton "kvo".

Ce qui suit est utilisé dans une aire de jeux:

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

Cette erreur:

erreur fatale: impossible d'extraire une chaîne de 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/Sib /public/SDK/Foundation/NSObject.Swift, ligne 85

Voir également cette erreur:

erreur: l'exécution a été interrompue, raison: EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP, sous-code = 0x0). Le processus a été laissé au point où il a été interrompu, utilisez "thread return -x" pour revenir à l'état avant l'évaluation de l'expression.

Est-ce que quelqu'un d'autre peut faire fonctionner quelque chose comme ça, ou est-ce un bug que je dois signaler?

14
possen

Le bogue ici est que le compilateur vous permet de dire:

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

Node est un struct, il ne peut donc pas être directement représenté dans Obj-C. Cependant, le compilateur vous permet toujours de marquer tr comme dynamic - ce qui nécessite @objc. Tandis que @objcMembers déduit @objc pour les membres de la classe, il ne le fait que pour les membres qui sont directement représentables dans Obj-C, ce que tr n'est pas.

Donc, vraiment, le compilateur ne devrait pas vous laisser marquer tr comme dynamic - Je suis allé de l'avant et a déposé un bogue ici , qui a maintenant été corrigé et sera prêt pour Swift 5.

tr doit être @objc & dynamic pour que vous puissiez utiliser KVO dessus, car KVO nécessite un swizzling de méthode, ce que le runtime Obj-C fournit, et Swift runtime ne le fait pas. Donc, pour utiliser KVO ici, vous devrez créer Node a class, et hériter de NSObject afin d'exposer 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
    }
}

(et si vous regardez à nouveau la vidéo WWDC, vous verrez que la propriété qu'ils observent est en fait de type a class qui hérite de NSObject)

Cependant, dans l'exemple que vous donnez, vous n'avez pas vraiment besoin de KVO - vous pouvez simplement conserver Node en tant que struct, et utiliser à la place un observateur de propriétés:

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

Et comme Node est un type de valeur, didSet se déclenchera également pour toute modification de ses propriétés:

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

Selon Apple, il s'agit du comportement prévu à l'heure actuelle car il dépend du runtime Objective-C. C'était leur réponse à mon rapport de bogue et cela confirme encore ce que l'affiche de réponse acceptée a dit.

1
possen

La réponse acceptée est juste. Mais je veux dire ce que je sais du KVO à Swift.

Swift mélange également la compilation OC dans de nombreux Kit et implémentations, comme KVO. Vous devez donc savoir comment KVO a été implémenté dans OC.

Lorsque vous addObserver: forKeyPath: Pour un objet, le runtime OC crée une sous-classe hérite de la classe à laquelle appartient l'objet, puis réécrit la méthode setter pour l'objet, lorsque l'objet est modifié , il appelle setter et setValue(_ value: Any?, forKey key: String) pour notifier la modification.

Maintenant, revenons à Swift, donc votre keyPath devrait être un type accepté par 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.

Au-dessus de ce que je sais sur KVO, je vais le rechercher et mettre à jour ma réponse en continu.

0
yuanjilee