Właściwości zmiennych tylko do odczytu i nieobliczane w języku Swift

126

Próbuję coś wymyślić w nowym języku Apple Swift. Powiedzmy, że robiłem coś podobnego do tego w Objective-C. mamreadonly właściwości i nie można ich zmieniać indywidualnie. Jednak przy użyciu określonej metody właściwości są zmieniane w logiczny sposób.

Biorę następujący przykład, bardzo prosty zegar. Napisałbym to w 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

W określonym celu nie możemy bezpośrednio dotykać sekund, minut i godzin, a przy użyciu metody można tylko zwiększać sekundę po sekundzie. Tylko ta metoda może zmieniać wartości przy użyciu sztuczek zmiennych instancji.

Ponieważ w Swift nie ma takich rzeczy, próbuję znaleźć odpowiednik. Jeśli to zrobię:

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

To by zadziałało, ale każdy mógłby bezpośrednio zmienić właściwości.

Może miałem już zły projekt w Objective-C i dlatego potencjalny nowy odpowiednik Swift nie ma sensu. Jeśli nie i jeśli ktoś ma odpowiedź, byłbym bardzo wdzięczny;)

Może obiecane przez Apple przyszłe mechanizmy kontroli dostępu są odpowiedzią?

Dzięki!

Zaxonov
źródło
2
Kontrola dostępu jest odpowiedzią. Utworzyłbyś właściwości obliczone tylko do odczytu, jednocześnie używając prywatnych właściwości dla rzeczywistych danych.
Leo Natan
Pamiętaj, aby otworzyć raport o błędzie (ulepszenie), aby poinformować Apple, jak bardzo tego brakuje.
Leo Natan
1
Dla nowych użytkowników: Przewiń w dół ...
Gustaf Rosenblad,
2
Oznacz odpowiedź @ Ethan jako poprawną.
phatmann

Odpowiedzi:

356

Po prostu poprzedź deklarację właściwości private(set), na przykład:

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

privateutrzymuje go lokalnie w pliku źródłowym, podczas gdy internalzachowuje go lokalnie dla modułu / projektu.

private(set)tworzy read-onlywłaściwość, jednocześnie privateustawiając obie seti getjako prywatną.

Ethan
źródło
28
„prywatne”, aby zachować je lokalnie w pliku źródłowym, „wewnętrzne”, aby zachować je lokalnie w module / projekcie. private (zestaw) sprawia, że ​​zestaw jest tylko prywatny. Prywatne ustawia i pobiera funkcje jako prywatne
user965972
3
Pierwszy komentarz należy dodać do tej odpowiedzi, aby była bardziej kompletna.
Rob Norback
W tej chwili (Swift 3.0.1) Zmieniono poziomy kontroli dostępu: do deklaracji „fileprivate” można uzyskać dostęp tylko za pomocą kodu w tym samym pliku źródłowym co deklaracja ”. Dostęp do deklaracji „prywatnej” można uzyskać tylko za pomocą kodu znajdującego się w bezpośrednim zasięgu deklaracji.
Jurasic
20

Istnieją dwa podstawowe sposoby robienia tego, co chcesz. Pierwszy sposób polega na posiadaniu własności prywatnej i publicznej obliczonej właściwości, która zwraca tę własność:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Ale można to osiągnąć w inny, krótszy sposób, jak podano w sekcji „Kontrola dostępu” książki „The Swift Programming Language” :

public class Clock {

    public private(set) var hours = 0

}

Na marginesie, podczas deklarowania typu publicznego MUSISZ podać publiczny inicjator. Nawet jeśli podasz wartości domyślne dla wszystkich właściwości, init()należy je zdefiniować jawnie publicznie:

public class Clock {

    public private(set) var hours = 0

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

}

Jest to również wyjaśnione w tej samej sekcji książki, gdy mowa o domyślnych inicjatorach.

elitalon
źródło
5

Ponieważ nie ma kontroli dostępu (co oznacza, że ​​nie można zawrzeć umowy dostępu, która różni się w zależności od tego, kto dzwoni), oto co bym teraz zrobił:

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

To tylko dodaje pewien poziom pośredni, jakby bezpośredni, dostęp do licznika; inna klasa może utworzyć zegar, a następnie uzyskać bezpośredni dostęp do jego licznika. Ale pomysł - tj. Kontrakt, który próbujemy zawrzeć - jest taki, że inna klasa powinna używać tylko właściwości i metod najwyższego poziomu Zegara. Nie możemy wyegzekwować tego kontraktu, ale w rzeczywistości było to prawie niemożliwe do wyegzekwowania również w celu C.

matowe
źródło
Wygląda na to, że to się zmieniło od tamtego czasu, prawda? developer.apple.com/swift/blog/?id=5
Dan Rosenstark
1
@ Jasne, całe to pytanie / odpowiedź zostało napisane na długo przed dodaniem kontroli dostępu do Swift. Swift wciąż się rozwija, więc odpowiedzi będą nadal nieaktualne.
mat
2

W rzeczywistości kontrola dostępu (która nie istnieje jeszcze w Swift) nie jest tak egzekwowana, jak mogłoby się wydawać w celu C. Ludzie mogą modyfikować zmienne tylko do odczytu, jeśli naprawdę tego chcą. Po prostu nie robią tego z publicznym interfejsem klasy.

Możesz zrobić coś podobnego w Swift (wytnij i wklej swój kod plus kilka modyfikacji, nie testowałem tego):

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

co jest tym samym, co masz w Objective C, z wyjątkiem tego, że rzeczywiste przechowywane właściwości są widoczne w interfejsie publicznym.

W swifcie można też zrobić coś ciekawszego, co też da się zrobić w Objective C, ale chyba ładniej jest w swifcie (edytowane w przeglądarce, nie testowałem):

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++
    }
}
Plik analogowy
źródło
„Ludzie mogą bezpośrednio modyfikować zmienne tylko do odczytu” - co dokładnie masz na myśli? W jaki sposób?
user1244109
Miałem na myśli Objective C. W Objective C masz dynamiczny dostęp do wszystkiego, co dotyczy klasy, poprzez API Objective C Runtime.
Plik analogowy
jakoś zrozumiałem, że zasugerowałeś to szybko. Zaciekawiło mnie. Dzięki
user1244109