Quel est le but de willSet et de didSet dans Swift?

Swift a une syntaxe de déclaration de propriété très similaire à celle de C #:

var foo: Int { get { return getFoo() } set { setFoo(newValue) } } 

Cependant, il a également des actions willSet et didSet . Ceux-ci sont appelés avant et après l’appel du composeur, respectivement. Quel est leur but, étant donné que vous pouvez simplement avoir le même code dans le setter?

Le point semble être que, parfois, vous avez besoin d’une propriété qui a un stockage automatique et un comportement, par exemple pour notifier les autres objects que la propriété vient de changer. Lorsque tout ce que vous avez est get / set , vous avez besoin d’un autre champ pour contenir la valeur. Avec willSet et didSet , vous pouvez agir lorsque la valeur est modifiée sans avoir besoin d’un autre champ. Par exemple, dans cet exemple:

 class Foo { var myProperty: Int = 0 { didSet { print("The value of myProperty changed from \(oldValue) to \(myProperty)") } } } 

myProperty imprime sa valeur ancienne et nouvelle à chaque modification. Avec juste des accesseurs et des régleurs, j’aurais besoin de ceci:

 class Foo { var myPropertyValue: Int = 0 var myProperty: Int { get { return myPropertyValue } set { print("The value of myProperty changed from \(myPropertyValue) to \(newValue)") myPropertyValue = newValue } } } 

Ainsi, willSet et didSet représentent une économie de quelques lignes et moins de bruit dans la liste des champs.

Ma compréhension est que set et get sont pour les propriétés calculées (pas de sauvegarde des propriétés stockées )

Si vous venez d’Objective-C, sachez que les conventions de nommage ont changé. Dans Swift, une variable d’instance ou d’instance est nommée propriété stockée

Exemple 1 (propriété en lecture seule) – avec avertissement:

 var test : Int { get { return test } } 

Cela se traduira par un avertissement car cela se traduit par un appel de fonction récursif (le getter s’appelle lui-même). L’avertissement dans ce cas est “Tentative de modification de ‘test’ dans son propre getter”.

Exemple 2. Lecture / écriture conditionnelle – avec avertissement

 var test : Int { get { return test } set (aNewValue) { //I've consortingved some condition on which this property can be set //(prevents same value being set) if (aNewValue != test) { test = aNewValue } } } 

Problème similaire – vous ne pouvez pas faire cela car il appelle récursivement le setter. Notez également que ce code ne se plaindra pas d’initialiseurs car il n’ya pas de propriété stockée à initialiser .

Exemple 3. Propriété calculée en lecture / écriture – avec magasin de sauvegarde

Voici un modèle qui permet le paramétrage conditionnel d’une propriété stockée réelle

 //True model data var _test : Int = 0 var test : Int { get { return _test } set (aNewValue) { //I've consortingved some condition on which this property can be set if (aNewValue != test) { _test = aNewValue } } } 

Remarque Les données réelles sont appelées _test (bien que cela puisse être n’importe quelle donnée ou combinaison de données). Notez également la nécessité de fournir une valeur initiale (vous devez également utiliser une méthode init) car _test est en fait une variable d’instance

Exemple 4. Utiliser will et set set

 //True model data var _test : Int = 0 { //First this willSet { println("Old value is \(_test), new value is \(newValue)") } //value is set //Finaly this didSet { println("Old value is \(oldValue), new value is \(_test)") } } var test : Int { get { return _test } set (aNewValue) { //I've consortingved some condition on which this property can be set if (aNewValue != test) { _test = aNewValue } } } 

Nous voyons ici willSet et didSet intercepter un changement dans une propriété stockée réelle. Ceci est utile pour envoyer des notifications, synchronisation, etc … (voir exemple ci-dessous)

Exemple 5. Exemple concret – Conteneur ViewController

 //Underlying instance variable (would ideally be private) var _childVC : UIViewController? { willSet { //REMOVE OLD VC println("Property will set") if (_childVC != nil) { _childVC!.willMoveToParentViewController(nil) self.setOverrideTraitCollection(nil, forChildViewController: _childVC) _childVC!.view.removeFromSuperview() _childVC!.removeFromParentViewController() } if (newValue) { self.addChildViewController(newValue) } } //I can't see a way to 'stop' the value being set to the same controller - hence the computed property didSet { //ADD NEW VC println("Property did set") if (_childVC) { // var views = NSDictionaryOfVariableBindings(self.view) .. NOT YET SUPPORTED (NSDictionary bridging not yet available) //Add subviews + constraints _childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false) //For now - until I add my own constraints self.view.addSubview(_childVC!.view) let views = ["view" : _childVC!.view] as NSMutableDictionary let layoutOpts = NSLayoutFormatOptions(0) let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|", options: layoutOpts, mesortingcs: NSDictionary(), views: views) let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, mesortingcs: NSDictionary(), views: views) self.view.addConstraints(lc1) self.view.addConstraints(lc2) //Forward messages to child _childVC!.didMoveToParentViewController(self) } } } //Computed property - this is the property that must be used to prevent setting the same value twice //unless there is another way of doing this? var childVC : UIViewController? { get { return _childVC } set(suggestedVC) { if (suggestedVC != _childVC) { _childVC = suggestedVC } } } 

Notez l’utilisation des deux propriétés calculées et stockées. J’ai utilisé une propriété calculée pour éviter de définir deux fois la même valeur (pour éviter que de mauvaises choses ne se produisent!); J’ai utilisé willSet et didSet pour transférer les notifications vers viewControllers (voir la documentation et les informations sur les conteneurs viewController dans UIViewController).

J’espère que cela aide, et s’il vous plaît quelqu’un crier si j’ai fait une erreur n’importe où ici!

Ceux-ci sont appelés observateurs de propriété :

Les observateurs de propriété observent et répondent aux changements de valeur d’une propriété. Les observateurs de propriétés sont appelés chaque fois que la valeur d’une propriété est définie, même si la nouvelle valeur est la même que la valeur actuelle de la propriété.

Extrait de: Apple Inc. «Le langage de programmation rapide». IBooks. https://itun.es/ca/jEUH0.l

Je soupçonne que c’est pour permettre des choses que nous ferions traditionnellement avec KVO telles que la liaison de données avec des éléments d’interface utilisateur, ou le déclenchement d’effets secondaires de la modification d’une propriété, le déclenchement d’un processus de synchronisation, le traitement en arrière-plan, etc.

REMARQUE

willSet observateurs willSet et didSet ne sont pas appelés lorsqu’une propriété est définie dans un initialiseur avant la délégation

Vous pouvez également utiliser didSet pour définir la variable sur une valeur différente. Cela ne provoque pas l’appel de l’observateur comme indiqué dans le guide Propriétés . Par exemple, il est utile lorsque vous souhaitez limiter la valeur ci-dessous:

 let minValue = 1 var value = 1 { didSet { if value < minValue { value = minValue } } } value = -10 // value is minValue now. 

Les nombreuses réponses déjà bien écrites couvrent bien la question, mais je mentionnerai en détail un ajout qui, à mon avis, mérite d’être couvert.


Les observateurs de propriétés willSet et didSet peuvent être utilisés pour appeler des delegates, par exemple, pour des propriétés de classe qui ne sont mises à jour que par une interaction utilisateur, mais pour éviter d’appeler le délégué lors de l’initialisation d’object.

Je cite le commentaire de Klaas à la réponse acceptée:

Les observateurs willSet et didSet ne sont pas appelés lorsqu’une propriété est initialisée pour la première fois. Ils ne sont appelés que lorsque la valeur de la propriété est définie en dehors d’un contexte d’initialisation.

Ceci est très intéressant car cela signifie par exemple que la propriété didSet est un bon choix de sharepoint lancement pour les callbacks et fonctions delegates, pour vos propres classes personnalisées.

À titre d’exemple, considérons un object de contrôle utilisateur personnalisé, avec une value propriété de clé (par exemple, position dans le contrôle d’évaluation), implémenté en tant que sous-classe de UIView :

 // CustomUserControl.swift protocol CustomUserControlDelegate { func didChangeValue(value: Int) // func didChangeValue(newValue: Int, oldValue: Int) // func didChangeValue(customUserControl: CustomUserControl) // ... other more sophisticated delegate functions } class CustomUserControl: UIView { // Properties // ... private var value = 0 { didSet { // Possibly do something ... // Call delegate. delegate?.didChangeValue(value) // delegate?.didChangeValue(value, oldValue: oldValue) // delegate?.didChangeValue(self) } } var delegate: CustomUserControlDelegate? // Initialization required init?(...) { // Initialise something ... // Eg 'value = 1' would not call didSet at this point } // ... some methods/actions associated with your user control. } 

Après quoi vos fonctions de délégué peuvent être utilisées, par exemple, dans certains contrôleurs de vue pour observer les changements de clé dans le modèle pour CustomViewController , un peu comme vous utiliseriez les fonctions de délégué inhérentes aux objects UITextFieldDelegate pour UITextField (par exemple textFieldDidEndEditing(...) ) .

Pour cet exemple simple, utilisez un rappel de délégué de la didSet de la propriété de classe didSet pour indiquer à un contrôleur de vue que l’un de ses points de vente a eu une mise à jour de modèle associée:

 // ViewController.swift Import UIKit // ... class ViewController: UIViewController, CustomUserControlDelegate { // Properties // ... @IBOutlet weak var customUserControl: CustomUserControl! override func viewDidLoad() { super.viewDidLoad() // ... // Custom user control, handle through delegate callbacks. customUserControl = self } // ... // CustomUserControlDelegate func didChangeValue(value: Int) { // do some stuff with 'value' ... } // func didChangeValue(newValue: Int, oldValue: Int) { // do some stuff with new as well as old 'value' ... // custom transitions? :) //} //func didChangeValue(customUserControl: CustomUserControl) { // // Do more advanced stuff ... //} } 

Ici, la propriété value a été encapsulée, mais généralement: dans de telles situations, veillez à ne pas mettre à jour la propriété value de l’object customUserControl dans la scope de la fonction déléguée associée (ici: didChangeValue() ) dans le contrôleur de vue ou vous vous retrouverez avec une récursion infinie.

Les observateurs willSet et didSet pour les propriétés chaque fois que la propriété reçoit une nouvelle valeur. Cela est vrai même si la nouvelle valeur est la même que la valeur actuelle.

Et notez que willSet besoin d’un nom de paramètre pour contourner, d’autre part, didSet ne le fait pas.

L’observateur didSet est appelé après la mise à jour de la valeur de la propriété. Il se compare à l’ancienne valeur. Si le nombre total d’étapes a augmenté, un message est imprimé pour indiquer combien de nouvelles étapes ont été effectuées. L’observateur didSet ne fournit pas de nom de paramètre personnalisé pour l’ancienne valeur et le nom par défaut de oldValue est utilisé à la place.

Getter et setter sont parfois trop lourds à mettre en œuvre pour observer les changements de valeur. Habituellement, cela nécessite des manipulations variables et des vérifications supplémentaires, et vous éviterez même ces petites tâches si vous écrivez des centaines de getters et de régleurs. Ces choses sont pour la situation.

Dans votre propre classe (de base), willSet et didSet sont assez redondants , car vous pouvez définir une propriété calculée (c.-à-d. Des méthodes _propertyVariable et set) qui accèdent à une _propertyVariable et effectue les pré-et post-recherches souhaitées .

Si, cependant , vous écrasez une classe où la propriété est déjà définie , les fonctions willSet et didSet sont utiles et non redondantes!

Lorsque vous utilisez des points de vente pour append une configuration, didSet est très utile.

 @IBOutlet weak var loginOrSignupButton: UIButton! { didSet { let title = NSLocalizedSsortingng("signup_required_button") loginOrSignupButton.setTitle(title, for: .normal) loginOrSignupButton.setTitle(title, for: .highlighted) } 

Je ne connais pas C #, mais avec un peu de conjecture je pense que je comprends quoi

 foo : int { get { return getFoo(); } set { setFoo(newValue); } } 

Est-ce que. Cela ressemble beaucoup à ce que vous avez dans Swift, mais ce n’est pas la même chose: dans Swift, vous n’avez ni getFoo ni setFoo . Ce n’est pas une petite différence: cela signifie que vous n’avez aucun stockage sous-jacent pour votre valeur.

Swift a stocké et calculé les propriétés.

Une propriété calculée a get et peut avoir set (si elle est accessible en écriture). Mais le code dans getter et setter, s’ils ont besoin de stocker certaines données, doit le faire dans d’ autres propriétés. Il n’y a pas de stockage de sauvegarde.

Une propriété stockée, en revanche, dispose d’un stockage de sauvegarde. Mais ça n’a pas de va et vient. Au lieu de cela, il y a willSet et didSet que vous pouvez utiliser pour observer les changements de variables et, éventuellement, déclencher des effets secondaires et / ou modifier la valeur stockée. Vous n’avez pas willSet et didSet pour les propriétés calculées et vous n’en avez pas besoin, car pour les propriétés calculées, vous pouvez utiliser le code set pour contrôler les modifications.