Programmation Swift: getter / setter dans la propriété stockée

102

Comment écraser le setter de la propriété stockée dans Swift?

Dans Obj-C, je peux écraser son setter, mais Swift ne semble pas être content de l'utilisation de getter / setters pour la propriété stockée.

Disons que j'ai une Cardclasse avec une propriété appelée rank. Je ne veux pas que le client lui donne une valeur invalide, par conséquent, dans objective-C, je peux écraser setRankpour qu'il effectue une vérification supplémentaire. Mais willSetdans Swift ne semble pas aider parce que newValuec'est constant et cela n'a aucun sens d'assigner rankparce que le setter sera appelé dans une boucle.

bohanl
la source
Avez-vous trouvé un moyen de faire cela? J'ai besoin de ce genre de fonctionnalité moi-même ...
Mihai Fratu
Je l'ai trouvé. Découvrez ma réponse ...
Mihai Fratu
Qu'en est-il de didGet ou analogique?
fnc12

Réponses:

107

D'accord. En lisant la documentation d'Apples sur Swift, j'ai trouvé ceci :

Si vous affectez une valeur à une propriété dans son propre observateur didSet, la nouvelle valeur que vous attribuez remplacera celle qui vient d'être définie.

Donc, tout ce que vous avez à faire est ceci:

var rank: Int = 0 {
    didSet {
        // Say 1000 is not good for you and 999 is the maximum you want to be stored there
        if rank >= 1000  {
            rank = 999
        }
    }
}
Mihai Fratu
la source
Qu'en est-il de didGet ou analogique?
fnc12
Je ne suis pas sûr de comprendre votre question. Pouvez-vous être plus précis, s'il vous plaît?
Mihai Fratu
J'ai besoin d'appeler du code avant d'obtenir. J'ai pu effectuer cela dans obj-c mais je ne vois pas comment effectuer cela dans Swift. La seule chose que je vois est d'utiliser deux propriétés: l'une est publique et l'autre est privée, public appelle mon code et renvoie la valeur de la propriété privée. C'est pourquoi j'ai posé une question sur didGet
fnc12
Vous ne pouvez avoir un getter que pour une propriété calculée. Par exemplevar rankTimesTwo: Int { get { return rank * 2 } }
Mihai Fratu
2
Fonctionne comme un charme! Attention, il ne sera pas appelé lors de la définition de la propriété dans init ()
Christoph
35

Vous ne pouvez pas remplacer get/ setpour une propriété stockée, mais vous pouvez utiliser des observateurs de propriété willSet/ didSet:

var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        println("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            println("Added \(totalSteps - oldValue) steps")
        }
    }
}

Les noms de paramètres par défaut sont newValuepour willSetet oldValuepour didSet, ou vous pouvez les nommer vous-même comme dans willSet(newTotalSteps).

Joseph Mark
la source
Cela marche. Mais cela ne résout pas mon problème, peut-être que je n'étais pas assez clair dans ma question initiale.
bohanl
Disons que j'ai une Cardclasse avec une propriété appelée rank. Je ne veux pas que le client lui donne une valeur, par conséquent, dans objective-C, je peux écraser setRankafin qu'il effectue une vérification supplémentaire. Mais willSetdans Swift ne semble pas aider parce que newValuec'est constant et cela n'a aucun sens d'assigner rankparce que le setter sera appelé dans une boucle.
bohanl
2
Je ne suis pas tout à fait sûr de ce que vous voulez dire, mais ne pourriez-vous pas utiliser didSetpour vérifier rankaprès avoir été défini et si la validation échoue, réinitialisez-le à autre chose, par exemple oldValue?
Joseph Mark
9

get et set sont pour les propriétés calculées (ils n'ont pas de stockage de sauvegarde). (À mon avis, le mot-clé 'var' est déroutant ici)

  • willSet et didSet sont appelés pour une variable d'instance (utilisez didSet pour remplacer les modifications)
  • set et get sont purement pour les propriétés calculées
user3675131
la source
9

Si vous ne souhaitez pas utiliser didSet, qui présente le problème que la valeur de la propriété est temporairement incorrecte, vous devez entourer une propriété calculée.

private var _foo:Int = 0
var foo:Int {
    get {
        return _foo
    }
    set {
        if(newValue > 999) {
            _foo = 999
        } else {
            _foo = newValue
        }
    }
}

Ou:

private var _foo:Int = 0
var foo:Int {
    get {
        return _foo
    }
    set {
        guard newValue <= 999 else {
            _foo = 999
            return
        }
        _foo = newValue
    }
}
Jim Driscoll
la source
Il n'est pas logique d'utiliser deux variables.
Piyush
1
Ceci n'utilise qu'une seule propriété (variable): fooest juste une expression calculée de _foo, ne vous laissez pas tromper par le mot-clé "var"! Cela signifie qu'il y a deux entrées accessibles à partir de l'espace de noms privé, mais cela n'a aucune incidence sur protected / public et garde la valeur de foovalide à tout moment. Il s'agit essentiellement du modèle «vue». Le problème que vous rencontrez avec la réécriture via didSet, en plus du fait qu'elle a une période d'invalidité, est qu'il y a un potentiel important pour une boucle infinie puisque vous réentrez le didSetgestionnaire de l'intérieur didSet.
Jim Driscoll
-8

Exemple simplifié:

class Shape {
    var sideLength: Double {
    get {
        return self.sideLength
    }
    set {
        // Implement the setter here.
        self.sideLength = newValue
    }
    }
}

Exemple complet

Découvrez perimetercet exemple.

Extrait de: Apple Inc. «The Swift Programming Language». iBooks. https://itun.es/us/jEUH0.l

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
    get {
        return 3.0 * sideLength
    }
    set {
        sideLength = newValue / 3.0
    }
    }

    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength”
Mike Rapadas
la source
3
dans ce cas, perimeterest toujours une propriété calculée. Comment écraser sideLength sans introduire de propriété calculée?
bohanl
@bohanl J'ai ajouté un exemple simplifié utilisant getetset
Mike Rapadas
6
Votre "exemple complet" montre une propriété calculée, pas une propriété stockée, et votre "exemple simplifié" ne fonctionne pas.
Caleb
Je me suis trompé. C'est comme si l'abstraction de @property (getters + setters auto-synthétisés) en Objective-C n'avait pas été résumée dans Swift. The
irony
6
Votre "exemple simplifié" appelle un getter dans un getter de lui-même. Boucle inf ... crash.
jakenberg