Propriétés des variables en lecture seule et non calculées dans Swift

126

J'essaie de comprendre quelque chose avec le nouveau langage Apple Swift. Disons que j'avais l'habitude de faire quelque chose comme ce qui suit en Objective-C. j'aireadonly propriétés et elles ne peuvent pas être modifiées individuellement. Cependant, en utilisant une méthode spécifique, les propriétés sont modifiées de manière logique.

Je prends l'exemple suivant, une horloge très simple. J'écrirais ceci en Objective-C.

@interface Clock : NSObject

@property (readonly) NSUInteger hours;
@property (readonly) NSUInteger minutes;
@property (readonly) NSUInteger seconds;

- (void)incrementSeconds;

@end

@implementation Clock

- (void)incrementSeconds {
     _seconds++;

     if (_seconds == 60) {
        _seconds = 0;
        _minutes++;

         if (_minutes == 60) {
            _minutes = 0;
            _hours++;
        }
    }
}

@end

Dans un but précis, nous ne pouvons pas toucher directement les secondes, les minutes et les heures, et il est seulement autorisé à incrémenter seconde par seconde à l'aide d'une méthode. Seule cette méthode pouvait changer les valeurs en utilisant l'astuce des variables d'instance.

Puisqu'il n'y a pas de telles choses dans Swift, j'essaie de trouver un équivalent. Si je fais ceci:

class Clock : NSObject {

    var hours:   UInt = 0
    var minutes: UInt = 0
    var seconds: UInt = 0

    func incrementSeconds() {

        self.seconds++

        if self.seconds == 60 {

            self.seconds = 0
            self.minutes++

            if self.minutes == 60 {

                self.minutes = 0
                self.hours++
            }
        }
    }
}

Cela fonctionnerait, mais n'importe qui pourrait changer directement les propriétés.

Peut-être que j'avais déjà une mauvaise conception en Objective-C et c'est pourquoi le nouvel équivalent potentiel de Swift n'a pas de sens. Sinon et si quelqu'un a une réponse, je serais très reconnaissant;)

Peut-être que les futurs mécanismes de contrôle d'accès promis par Apple sont la réponse?

Merci!

Zaxonov
la source
2
Le contrôle d'accès est la réponse. Vous créeriez des propriétés calculées en lecture seule, tout en utilisant des propriétés privées pour les données réelles.
Leo Natan
Assurez-vous d'ouvrir un rapport de bogue (amélioration) pour indiquer à Apple à quel point cela manque.
Leo Natan
1
Pour les nouveaux utilisateurs: faites défiler vers le bas ...
Gustaf Rosenblad
2
Veuillez marquer la réponse de @ Ethan comme correcte.
phatmann
1
Possible duplicata de Swift readonly external, readwrite internal property
Beau Nouvelle

Réponses:

356

Préfixez simplement la déclaration de propriété avec private(set), comme ceci:

public private(set) var hours:   UInt = 0
public private(set) var minutes: UInt = 0
public private(set) var seconds: UInt = 0

privatele garde local dans un fichier source, tout en le internalgardant local dans le module / projet.

private(set)crée une read-onlypropriété, tandis que privatedéfinit à la fois setet getsur private.

Ethan
la source
28
"private" pour le garder local dans un fichier source, "internal" pour le garder local dans le module / projet. private (set) ne rend l'ensemble privé. Privé rend les fonctions définies et
récupérées
3
Le premier commentaire doit être ajouté à cette réponse pour la rendre plus complète.
Rob Norback
En ce moment (Swift 3.0.1) Niveaux de contrôle d'accès modifiés: la déclaration "fileprivate" n'est accessible que par code dans le même fichier source que la déclaration ». La déclaration "privée" n'est accessible que par code dans la portée immédiate de la déclaration.
Jurasic
20

Il existe deux façons de faire ce que vous voulez. La première consiste à avoir une propriété privée et et une propriété publique calculée qui retourne cette propriété:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Mais cela peut être réalisé d'une manière différente et plus courte, comme indiqué dans la section "Contrôle d'accès" du livre "The Swift Programming Language" :

public class Clock {

    public private(set) var hours = 0

}

En remarque, vous DEVEZ fournir un initialiseur public lors de la déclaration d'un type public. Même si vous fournissez des valeurs par défaut à toutes les propriétés, init()doit être défini explicitement public:

public class Clock {

    public private(set) var hours = 0

    public init() {
      hours = 0
    }
    // or simply `public init() {}`

}

Ceci est également expliqué dans la même section du livre, quand on parle des initialiseurs par défaut.

élitalon
la source
5

Puisqu'il n'y a pas de contrôle d'accès (ce qui signifie que vous ne pouvez pas faire un contrat d'accès qui diffère selon l'appelant), voici ce que je ferais pour l'instant:

class Clock {
    struct Counter {
        var hours = 0;
        var minutes = 0;
        var seconds = 0;
        mutating func inc () {
            if ++seconds >= 60 {
                seconds = 0
                if ++minutes >= 60 {
                    minutes = 0
                    ++hours
                }
            }
        }
    }
    var counter = Counter()
    var hours : Int { return counter.hours }
    var minutes : Int { return counter.minutes }
    var seconds : Int { return counter.seconds }
    func incrementTime () { self.counter.inc() }
}

Cela ajoute simplement un niveau d'indirection, pour ainsi dire, à l'accès direct au compteur; une autre classe peut créer une horloge et accéder directement à son compteur. Mais l' idée - c'est-à-dire le contrat que nous essayons de faire - est qu'une autre classe ne devrait utiliser que les propriétés et méthodes de premier niveau de Clock. Nous ne pouvons pas appliquer ce contrat, mais en fait, il était pratiquement impossible de le faire également en Objective-C.

mat
la source
On dirait que cela a changé depuis, n'est-ce pas? developer.apple.com/swift/blog/?id=5
Dan Rosenstark
1
@Yar bien sûr, toute cette question / réponse a été écrite bien avant que le contrôle d'accès ne soit ajouté à Swift. Et Swift évolue toujours , les réponses resteront donc obsolètes.
mat
2

En fait, le contrôle d'accès (qui n'existe pas encore dans Swift) n'est pas aussi appliqué que vous pouvez le penser dans l'Objectif C. Les gens peuvent modifier vos variables en lecture seule directement, s'ils le souhaitent vraiment. Ils ne le font tout simplement pas avec l'interface publique de la classe.

Vous pouvez faire quelque chose de similaire dans Swift (couper-coller de votre code, plus quelques modifications, je ne l'ai pas testé):

class Clock : NSObject {

    var _hours:   UInt = 0
    var _minutes: UInt = 0
    var _seconds: UInt = 0

    var hours: UInt {
    get {
      return _hours
    }
    }

    var minutes: UInt {
    get {
      return _minutes
    }
    }

    var seconds: UInt  {
    get {
      return _seconds
    }
    }

    func incrementSeconds() {

        self._seconds++

        if self._seconds == 60 {

            self._seconds = 0
            self._minutes++

            if self._minutes == 60 {

                self._minutes = 0
                self._hours++
            }
        }
    }
}

qui est identique à ce que vous avez dans Objective C sauf que les propriétés stockées réelles sont visibles dans l'interface publique.

En swift, vous pouvez aussi faire quelque chose de plus intéressant, ce que vous pouvez également faire en Objective C, mais c'est probablement plus joli en swift (édité dans le navigateur, je ne l'ai pas testé):

class Clock : NSObject {

    var hours: UInt = 0

    var minutes: UInt {
    didSet {
      hours += minutes / 60
      minutes %= 60
    }
    }

    var seconds: UInt  {
    didSet {
      minutes += seconds / 60
      seconds %= 60
    }
    }

    // you do not really need this any more
    func incrementSeconds() {
        seconds++
    }
}
Fichier analogique
la source
"Les gens peuvent modifier directement vos variables en lecture seule" - que voulez-vous dire exactement? Comment?
user1244109
Je faisais référence à Objective C. Dans Objective C, vous avez un accès dynamique à tout ce qui concerne une classe via les API Objective C Runtime.
Fichier analogique
J'ai en quelque sorte compris que vous avez laissé entendre que pour rapide. Je suis curieux. Merci
user1244109