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