web-dev-qa-db-fra.com

Propriétés de variable en lecture seule et non calculées dans Swift

J'essaie de trouver quelque chose avec la nouvelle langue Apple Swift. Disons que j'avais l'habitude de faire quelque chose comme ce qui suit dans Objective-C. J'ai readonly propriétés, et ils ne peuvent pas être modifiés individuellement.Toutefois, 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 spécifique, nous ne pouvons pas toucher les secondes, les minutes et les heures directement, et il est seulement permis d'incrémenter seconde par seconde en utilisant une méthode. Seule cette méthode peut changer les valeurs en utilisant l'astuce des variables d'instance.

Comme il n’existe pas de telles choses à Swift, j’essaie de trouver un équivalent. Si je fais ça:

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.

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

Peut-être que l'avenir Mécanismes de contrôle d'accès promis par Apple est la réponse?)

Merci!

119
Zaxonov

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

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

private le conserve en local dans un fichier source, tandis que internal le conserve en local dans le module/projet.

private(set) crée une propriété read-only, tandis que private définit à la fois privé, set et get.

346
Ethan

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

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Mais ceci peut être réalisé d'une manière différente, plus courte, comme indiqué dans la section "Contrôle d'accès" du "Le Swift Langage de programmation" livre:

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 comme 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 d'initialiseurs par défaut.

20
elitalon

Puisqu'il n'y a pas de contrôle d'accès (ce qui signifie que vous ne pouvez pas créer de contrat d'accès différent 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, pour diriger l'accès au compteur; une autre classe peut fabrique une horloge puis accède directement à son compteur. Mais la idée - c’est-à-dire le contrat que nous essayons de conclure - est qu’une autre classe devrait utiliser uniquement les propriétés et méthodes de niveau supérieur de Clock. Nous ne pouvons pas appliquer ce contrat, mais en réalité, il était pratiquement impossible de le faire également en Objective-C.

5
matt

En réalité, le contrôle d’accès (qui n’existe pas encore dans Swift) n’est pas aussi strict que l’on peut le penser dans l’objectif C. Personnes peut modifier directement vos variables en lecture seule, si elles 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 (copier/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++
            }
        }
    }
}

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

Dans Swift vous pouvez également faire quelque chose de plus intéressant, que vous pouvez également faire dans Objective C, mais c'est probablement plus joli dans Swift (édité dans le navigateur, je ne l'a 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++
    }
}
2
Analog File