web-dev-qa-db-fra.com

L'observation clé-valeur (KVO) est-elle disponible dans Swift?

Si tel est le cas, y a-t-il des différences clés qui n'étaient pas autrement présentes lors de l'utilisation de l'observation valeur-clé dans Objective-C?

158
jonsibley

Oui et non. KVO fonctionne sur les sous-classes NSObject comme il l’a toujours fait. Cela ne fonctionne pas pour les classes qui ne sous-classent pas NSObject. Swift ne possède pas (actuellement au moins) son propre système d'observation natif.

(Voir les commentaires pour savoir comment exposer d'autres propriétés comme ObjC afin que KVO fonctionne dessus)

Voir le Documentation Apple pour un exemple complet.

102
Catfish_Man

Vous pouvez utiliser KVO dans Swift, mais uniquement pour les propriétés dynamic de la sous-classe NSObject. Considérez que vous vouliez observer la propriété bar d'une classe Foo. Dans Swift 4, spécifiez bar comme propriété dynamic dans votre sous-classe NSObject:

class Foo: NSObject {
    @objc dynamic var bar = 0
}

Vous pouvez ensuite vous inscrire pour observer les modifications apportées à la propriété bar. Dans Swift 4 et Swift 3.2, cela a été grandement simplifié, comme indiqué dans tilisation de l'observation de la valeur clé dans Swift :

class MyObject {
    private var token: NSKeyValueObservation

    var objectToObserve = Foo()

    init() {
        token = objectToObserve.observe(\.bar) { [weak self] object, change in  // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
            print("bar property is now \(object.bar)")
        }
    }
}

Notez que, dans Swift 4, nous avons maintenant une forte saisie des raccourcis clavier utilisant le caractère barre oblique inversée (le \.bar est le raccourci clavier de la propriété bar de l'objet observé). De plus, comme il utilise le modèle de fin d'exécution, nous n'avons pas besoin de supprimer manuellement les observateurs (lorsque la token tombe en dehors de la portée, l'observateur est supprimé pour nous) et nous n'avons pas à nous soucier d'appeler le super mise en œuvre si la clé ne correspond pas. La fermeture est appelée uniquement lorsque cet observateur particulier est appelé. Pour plus d'informations, voir la vidéo WWDC 2017, Nouveautés de Foundation .

Dans Swift 3, observer ceci est un peu plus compliqué, mais très similaire à ce que l'on fait en Objective-C. En d'autres termes, vous implémenteriez observeValue(forKeyPath keyPath:, of object:, change:, context:) qui (a) garantit que nous traitons notre contexte (et non quelque chose que notre instance super s'était enregistrée pour observer); et ensuite (b) soit le gérer, soit le transmettre à la mise en oeuvre super, si nécessaire. Et assurez-vous de vous retirer en tant qu'observateur, le cas échéant. Par exemple, vous pouvez supprimer l’observateur lorsqu’il est désalloué:

Dans Swift 3:

class MyObject: NSObject {
    private var observerContext = 0

    var objectToObserve = Foo()

    override init() {
        super.init()

        objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
    }

    deinit {
        objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard context == &observerContext else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
            return
        }

        // do something upon notification of the observed object

        print("\(keyPath): \(change?[.newKey])")
    }

}

Notez que vous ne pouvez observer que les propriétés pouvant être représentées dans Objective-C. Ainsi, vous ne pouvez pas observer les génériques, les types Swift struct, les types Swift enum, etc.

Pour une discussion sur l'implémentation Swift 2, voir ma réponse initiale, ci-dessous.


L'utilisation du mot clé dynamic pour obtenir le KVO avec les sous-classes NSObject est décrite dans la section Observation de la valeur-clé de la section Adoption de conventions de conception de cacao chapitre du guide Utilisation de Swift avec Cocoa et Objective-C :

L'observation des valeurs-clés est un mécanisme qui permet aux objets d'être informés des modifications apportées aux propriétés spécifiées d'autres objets. Vous pouvez utiliser l'observation valeur-clé avec une classe Swift, tant que la classe hérite de la classe NSObject. Vous pouvez utiliser ces trois étapes pour implémenter l'observation valeur-clé dans Swift.

  1. Ajoutez le modificateur dynamic à toute propriété à observer. Pour plus d'informations sur dynamic, reportez-vous à la section Requérir une répartition dynamique .

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
    
  2. Créez une variable de contexte global.

    private var myContext = 0
    
  3. Ajoutez un observateur pour le chemin d'accès clé, substituez la méthode observeValueForKeyPath:ofObject:change:context: et supprimez l'observateur dans deinit.

    class MyObserver: NSObject {
        var objectToObserve = MyObjectToObserve()
        override init() {
            super.init()
            objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
        }
    
        override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
            if context == &myContext {
                if let newValue = change?[NSKeyValueChangeNewKey] {
                    print("Date changed: \(newValue)")
                }
            } else {
                super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            }
        }
    
        deinit {
            objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
        }
    }
    

[Remarque: cette discussion KVO a par la suite été retirée du guide Utilisation de Swift avec Cocoa et Objective-C , qui a été adapté pour Swift 3, mais cela fonctionne toujours comme indiqué en haut de cette réponse.]


Il est à noter que Swift a son propre système observateur de propriété , mais c'est pour une classe spécifiant son propre code qui sera exécuté lors de l'observation de ses propres propriétés. KVO, en revanche, est conçu pour s'inscrire afin d'observer les modifications apportées à une propriété dynamique d'une autre classe.

143
Rob

Oui et non:

  • Oui , vous pouvez utiliser les mêmes anciennes API KVO dans Swift pour observer les objets Objective-C.
    Vous pouvez également observer les propriétés dynamic des objets Swift héritant de NSObject.
    Mais ... Non ce n’est pas très typé, comme on pouvait s’y attendre Swift comme système d’observation natif.
    tilisation de Swift avec Cocoa et Objective-C | Key Value Observing

  • Non , il n'existe actuellement aucun système d'observation des valeurs intégrées pour les objets Swift arbitraires.

  • Oui , il y a des éléments intégrés Property Observers, qui sont fortement typés.
    Mais ... Non ils ne sont pas du KVO, car ils ne permettent que d'observer les propriétés propres des objets, ne prennent pas en charge les observations imbriquées. ("chemins clés"), et vous devez les implémenter explicitement.
    Le Swift langage de programmation | Observateurs de propriétés

  • Oui , vous pouvez implémenter l'observation de valeur explicite, qui sera fortement typée, et permettre l'ajout de plusieurs gestionnaires à partir d'autres objets, et même prendre en charge l'emboîtement/"clé chemins ".
    Mais ... Non il ne s'agira pas de KVO car il ne fonctionnera que pour les propriétés que vous implémenterez en tant qu'observables.
    Vous pouvez trouver une bibliothèque pour implémenter une telle valeur en observant ici:
    Observable-Swift - KVO pour Swift - Observation de la valeur et événements

91
slazyk

Un exemple pourrait aider un peu ici. Si j'ai une instance model de la classe Model avec les attributs name et state, je peux observer ces attributs avec:

let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior])

model.addObserver(self, forKeyPath: "name", options: options, context: nil)
model.addObserver(self, forKeyPath: "state", options: options, context: nil)

Les modifications apportées à ces propriétés déclencheront un appel à:

override func observeValueForKeyPath(keyPath: String!,
    ofObject object: AnyObject!,
    change: NSDictionary!,
    context: CMutableVoidPointer) {

        println("CHANGE OBSERVED: \(change)")
}
10
Paul Robinson

Oui.

KVO requiert une répartition dynamique, il vous suffit donc d'ajouter le modificateur dynamic à une méthode, une propriété, un indice ou un initialiseur:

dynamic var foo = 0

Le modificateur dynamic garantit que les références à la déclaration seront distribuées de manière dynamique et accessibles via objc_msgSend.

8
Bryan Luby

En plus de la réponse de Rob. Cette classe doit hériter de NSObject, et nous avons 3 façons de déclencher un changement de propriété

Utilisez setValue(value: AnyObject?, forKey key: String) à partir de NSKeyValueCoding

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        setValue(NSDate(), forKey: "myDate")
    }
}

Utilisez willChangeValueForKey et didChangeValueForKey à partir de NSKeyValueObserving

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        willChangeValueForKey("myDate")
        myDate = NSDate()
        didChangeValueForKey("myDate")
    }
}

Utilisez dynamic. Voir Compatibilité Swift Type

Vous pouvez également utiliser le modificateur dynamique pour exiger que l'accès aux membres soit réparti de manière dynamique via le runtime d'Objective-C si vous utilisez des API, telles que l'observation clé-valeur, qui remplacent dynamiquement l'implémentation d'une méthode.

class MyObjectToObserve: NSObject {
    dynamic var myDate = NSDate()
    func updateDate() {
        myDate = NSDate()
    }
}

Et la propriété getter et setter est appelée lorsqu'elle est utilisée. Vous pouvez vérifier lorsque vous travaillez avec KVO. Ceci est un exemple de propriété calculée

class MyObjectToObserve: NSObject {
    var backing: NSDate = NSDate()
    dynamic var myDate: NSDate {
        set {
            print("setter is called")
            backing = newValue
        }
        get {
            print("getter is called")
            return backing
        }
    }
}
5
onmyway133

Actuellement, Swift ne prend en charge aucun mécanisme intégré permettant d’observer les modifications de propriétés d’objets autres que 'self'. Par conséquent, non, il ne prend pas en charge KVO.

Cependant, le KVO est un élément tellement fondamental d'Objective-C et de cacao qu'il semble tout à fait probable qu'il soit ajouté à l'avenir. La documentation actuelle semble impliquer ceci:

Observation de la valeur clé

Informations à venir.

tilisation de Swift avec Cocoa et Objective-C

5
ColinE

Une chose importante à mentionner est que, après la mise à jour de votre Xcode à 7 bêta, le message suivant peut s'afficher: "La méthode ne remplace aucune méthode de sa super-classe ". C'est à cause du caractère optionnel des arguments. Assurez-vous que votre gestionnaire d'observation ressemble exactement à ce qui suit:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)
4
Arthur Gevorkyan

Cela peut être utile à quelques personnes -

// MARK: - KVO

var observedPaths: [String] = []

func observeKVO(keyPath: String) {
    observedPaths.append(keyPath)
    addObserver(self, forKeyPath: keyPath, options: [.old, .new], context: nil)
}

func unObserveKVO(keyPath: String) {
    if let index = observedPaths.index(of: keyPath) {
        observedPaths.remove(at: index)
    }
    removeObserver(self, forKeyPath: keyPath)
}

func unObserveAllKVO() {
    for keyPath in observedPaths {
        removeObserver(self, forKeyPath: keyPath)
    }
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let keyPath = keyPath {
        switch keyPath {
        case #keyPath(camera.iso):
            slider.value = camera.iso
        default:
            break
        }
    }
}

J'avais utilisé KVO de cette manière dans Swift 3. Vous pouvez utiliser ce code avec peu de modifications.

3
Nikhil Manapure

Un autre exemple pour quiconque rencontre un problème avec des types tels que Int? et CGFloat ?. Vous définissez simplement votre classe en tant que sous-classe de NSObject et déclarez vos variables comme suit, par exemple:

class Theme : NSObject{

   dynamic var min_images : Int = 0
   dynamic var moreTextSize : CGFloat = 0.0

   func myMethod(){
       self.setValue(value, forKey: "\(min_images)")
   }

}
1
DrPatience