Schreibgeschützte und nicht berechnete Variableneigenschaften in Swift

126

Ich versuche mit der neuen Apple Swift-Sprache etwas herauszufinden. Nehmen wir an, ich habe in Objective-C so etwas wie das Folgende gemacht. Ich habe readonlyEigenschaften und sie können nicht individuell geändert werden. Mit einer bestimmten Methode werden die Eigenschaften jedoch auf logische Weise geändert.

Ich nehme das folgende Beispiel, eine sehr einfache Uhr. Ich würde dies in Objective-C schreiben.

@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

Für einen bestimmten Zweck können wir die Sekunden, Minuten und Stunden nicht direkt berühren, und es ist nur zulässig, Sekunden für Sekunden mithilfe einer Methode zu erhöhen. Nur diese Methode kann die Werte mithilfe des Tricks der Instanzvariablen ändern.

Da es in Swift keine solchen Dinge gibt, versuche ich, ein Äquivalent zu finden. Wenn ich das mache:

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++
            }
        }
    }
}

Das würde funktionieren, aber jeder könnte die Eigenschaften direkt ändern.

Vielleicht hatte ich bereits ein schlechtes Design in Objective-C und deshalb macht das potenzielle neue Swift-Äquivalent keinen Sinn. Wenn nicht und wenn jemand eine Antwort hat, wäre ich sehr dankbar;)

Vielleicht sind die von Apple versprochenen zukünftigen Zugriffskontrollmechanismen die Antwort?

Vielen Dank!

Zaxonov
quelle
2
Zugangskontrolle ist die Antwort. Sie würden schreibgeschützte berechnete Eigenschaften erstellen und private Eigenschaften für die tatsächlichen Daten verwenden.
Leo Natan
Stellen Sie sicher, dass Sie einen Fehlerbericht (Erweiterung) öffnen, um Apple darüber zu informieren, wie dringend dies fehlt.
Leo Natan
1
Für neue Benutzer: Scrollen Sie nach unten ...
Gustaf Rosenblad
2
Bitte markieren Sie die Antwort von @ Ethan als richtig.
Phatmann

Antworten:

356

Stellen Sie der Eigenschaftsdeklaration einfach Folgendes vor private(set):

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

privateHält es lokal für eine Quelldatei, während internales lokal für das Modul / Projekt bleibt.

private(set)Erstellt eine read-onlyEigenschaft, während privatebeide setund getprivat festgelegt werden.

Ethan
quelle
28
"privat", um es lokal für eine Quelldatei zu halten, "intern", um es lokal für das Modul / Projekt zu halten. private (set) macht das Set nur privat. Private macht sowohl Set- als auch Get-Funktionen privat
user965972
3
Der erste Kommentar sollte zu dieser Antwort hinzugefügt werden, um sie vollständiger zu machen.
Rob Norback
Derzeit (Swift 3.0.1) wurden die Zugriffssteuerungsstufen geändert: Auf die Deklaration "fileprivate" kann nur über Code in derselben Quelldatei wie die Deklaration zugegriffen werden. Auf "private" Deklarationen kann nur über Code zugegriffen werden, der dem unmittelbaren Umfang der Deklaration entspricht.
Jurasic
20

Es gibt zwei grundlegende Möglichkeiten, um das zu tun, was Sie wollen. Der erste Weg besteht darin, ein privates Eigentum und ein öffentliches berechnetes Eigentum zu haben, das dieses Eigentum zurückgibt:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Dies kann jedoch auf eine andere, kürzere Weise erreicht werden, wie im Abschnitt "Zugriffskontrolle" des Buches "The Swift Programming Language" angegeben :

public class Clock {

    public private(set) var hours = 0

}

Als Randnotiz MÜSSEN Sie einen öffentlichen Initialisierer angeben, wenn Sie einen öffentlichen Typ deklarieren. Auch wenn Sie für alle Eigenschaften Standardwerte angeben, init()muss diese explizit öffentlich definiert werden:

public class Clock {

    public private(set) var hours = 0

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

}

Dies wird auch im selben Abschnitt des Buches erläutert, wenn es um Standardinitialisierer geht.

Elitalon
quelle
5

Da es keine Zugriffskontrollen gibt (was bedeutet, dass Sie keinen Zugriffsvertrag abschließen können, der je nach Anrufer unterschiedlich ist), würde ich Folgendes vorerst tun:

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() }
}

Dies fügt lediglich eine Ebene der Indirektion hinzu, um den direkten Zugriff auf den Zähler zu ermöglichen. Eine andere Klasse kann eine Uhr erstellen und dann direkt auf ihren Zähler zugreifen. Die Idee - dh der Vertrag, den wir abschließen möchten - ist jedoch, dass eine andere Klasse nur die Eigenschaften und Methoden der obersten Ebene der Uhr verwenden sollte. Wir können diesen Vertrag nicht durchsetzen , aber tatsächlich war es so gut wie unmöglich, ihn auch in Objective-C durchzusetzen.

matt
quelle
Sieht so aus, als hätte sich das seitdem geändert, richtig? developer.apple.com/swift/blog/?id=5
Dan Rosenstark
1
@Yar sicher, diese ganze Frage / Antwort wurde geschrieben, lange bevor die Zugriffskontrolle zu Swift hinzugefügt wurde. Und Swift ist immer noch weiterentwickelt, so Antworten veraltet sein wird.
Matt
2

Tatsächlich ist die Zugriffskontrolle (die in Swift noch nicht vorhanden ist) nicht so erzwungen, wie Sie vielleicht in Ziel C denken. Benutzer können Ihre schreibgeschützten Variablen direkt ändern, wenn sie dies wirklich möchten. Sie tun es einfach nicht mit der öffentlichen Schnittstelle der Klasse.

Sie können in Swift etwas Ähnliches tun (Ausschneiden und Einfügen Ihres Codes sowie einige Änderungen, die ich nicht getestet habe):

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++
            }
        }
    }
}

Dies entspricht dem, was Sie in Ziel C haben, außer dass die tatsächlich gespeicherten Eigenschaften in der öffentlichen Oberfläche sichtbar sind.

In Swift können Sie auch etwas Interessanteres tun, was Sie auch in Objective C tun können, aber in Swift ist es wahrscheinlich schöner (im Browser bearbeitet, ich habe es nicht getestet):

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++
    }
}
Analoge Datei
quelle
"Leute können Ihre schreibgeschützten Variablen direkt ändern" - was meinst du genau? Wie?
user1244109
Ich habe mich auf Ziel C bezogen. In Ziel C haben Sie über die Objective C-Laufzeit-APIs dynamischen Zugriff auf alles über eine Klasse.
Analoge Datei
Ich habe irgendwie verstanden, dass du das für schnell impliziert hast. Bin neugierig geworden. Danke
user1244109