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 comporte également des actions willSet
et didSet
. Ceux-ci sont appelés avant et après l'appel du setter, respectivement. Quel est leur but, étant donné que vous pourriez avoir le même code dans le setter?
Le problème semble être que, parfois, vous avez besoin d'une propriété qui possède un stockage automatique et un comportement, par exemple pour notifier à d'autres objets que la propriété vient d'être modifiée. 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 son ancienne et sa nouvelle valeur chaque fois qu'elle est modifiée. Avec juste des accesseurs et des setters, il me faudrait plutôt 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 de champs.
D'après ce que je comprends, set et get sont for propriétés calculées (aucun support de propriétés stockées )
si vous venez d'un Objective-C, gardez à l'esprit que les conventions de dénomination ont changé. Dans Swift, une variable d'instance ou iVar est nommée propriété stockée
var test : Int {
get {
return test
}
}
Cela entraînera un avertissement, car il en résultera un appel de fonction récursif (le getter s'appellera lui-même). L'avertissement dans ce cas est "Tentative de modification de 'test' dans son propre getter".
var test : Int {
get {
return test
}
set (aNewValue) {
//I've contrived some condition on which this property can be set
//(prevents same value being set)
if (aNewValue != test) {
test = aNewValue
}
}
}
Problème similaire - tu ne peux pas faire ça comme il appelle récursivement le passeur. Notez également que ce code ne se plaindra pas de l'absence d'initialiseurs car il n'y a pas de propriété stockée à initialiser.
Voici un modèle qui permet la définition conditionnelle 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 contrived 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 (sinon, vous devez utiliser une méthode init) car _test est en réalité une variable d'instance.
//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 contrived 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, synchroniser etc ... (voir exemple ci-dessous)
//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, metrics: NSDictionary(), views: views)
let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: 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 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 transmettre des notifications à viewControllers (voir la documentation sur UIViewController et des informations sur les conteneurs viewController).
J'espère que cela vous aidera, et s'il vous plaît, criez si j'ai commis une erreur n'importe où ici!
Ceux-ci sont appelés Observateurs de propriété :
Les observateurs de biens observent et réagissent aux changements de la valeur d’un bien. Les observateurs de propriété sont appelés chaque fois qu'une valeur de propriété est définie, même si la nouvelle valeur est identique à la valeur actuelle de la propriété.
Extrait de: Apple Inc. "Le langage de programmation Swift." IBooks. https://itun.es/ca/jEUH0.l
Je suppose que c'est pour permettre des tâches 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. .
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 Guide de propriétés . Par exemple, il est utile lorsque vous souhaitez limiter la valeur comme ci-dessous:
let minValue = 1
var value = 1 {
didSet {
if value < minValue {
value = minValue
}
}
}
value = -10 // value is minValue now.
REMARQUE
Les observateurs
willSet
etdidSet
ne sont pas appelés lorsqu'une propriété est définie dans un initialiseur avant la délégation.
Les nombreuses réponses existantes bien rédigées couvrent bien la question, mais je mentionnerai plus en détail un ajout qui, à mon avis, mérite d'être couvert.
Les observateurs de propriété willSet
et didSet
peuvent être utilisés pour appeler des délégués, par exemple pour des propriétés de classe qui ne sont jamais mises à jour par interaction de l'utilisateur, mais pour lesquelles vous souhaitez éviter d'appeler le délégué lors de l'initialisation de l'objet.
Je citerai le commentaire voté de Klaas à la réponse acceptée:
les observateurs willSet et didSet ne sont pas appelés lors de la première initialisation d'une propriété. Ils ne sont appelés que lorsque la valeur de la propriété est définie en dehors d’un contexte d’initialisation.
C’est une idée très soignée car cela signifie par exemple la propriété didSet
est un bon choix de point de lancement pour les rappels et les fonctions de délégué, pour vos propres classes personnalisées.
A titre d'exemple, considérons un objet de contrôle utilisateur personnalisé, avec une propriété de clé value
(par exemple, la 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 ...
// E.g. '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 un contrôleur de vue pour observer les changements de clé dans le modèle de CustomViewController
, comme vous utiliseriez les fonctions de délégué inhérentes à UITextFieldDelegate
pour UITextField
objets (par exemple textFieldDidEndEditing(...)
).
Pour cet exemple simple, utilisez un rappel de délégué à partir de didSet
de la propriété de classe value
pour indiquer à un contrôleur de vue que l'une de ses sorties a été mise à jour de modèle:
// 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'objet customUserControl
dans l'étendue de la fonction de délégué associée ( ici: didChangeValue()
) dans le contrôleur de vue, ou vous obtiendrez une récursion infinie.
Les observateurs willSet et didSet pour les propriétés chaque fois qu'une nouvelle valeur est attribuée à la propriété. Cela est vrai même si la nouvelle valeur est identique à la valeur actuelle.
Et notez que willSet
a 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 le nombre de nouvelles étapes. L'observateur didSet ne fournit pas de nom de paramètre personnalisé pour l'ancienne valeur. Le nom par défaut de oldValue est utilisé à la place.
Getter et setter sont parfois trop lourds à mettre en œuvre juste pour observer les changements de valeur appropriés. Habituellement, cela nécessite une manipulation de variable temporaire supplémentaire et des vérifications supplémentaires, et vous voudrez éviter même ces tâches minuscules si vous écrivez des centaines de getters et de setters. Ces trucs sont pour la situation.
Dans votre propre classe (de base), willSet
et didSet
sont assez reduntant, car vous pourriez définir une propriété calculée (c'est-à-dire, des méthodes get et set) qui accèdent à un _propertyVariable
et fait le désirée avant et après la prosession.
Si, cependant, vous écrasez une classe dont la propriété est déjà définie, alors les willSet
et didSet
sont tile et non redondant!
Une chose où didSet
est vraiment pratique est lorsque vous utilisez les prises pour ajouter une configuration supplémentaire.
@IBOutlet weak var loginOrSignupButton: UIButton! {
didSet {
let title = NSLocalizedString("signup_required_button")
loginOrSignupButton.setTitle(title, for: .normal)
loginOrSignupButton.setTitle(title, for: .highlighted)
}