it-swarm.xyz

Свойства только для чтения и не вычисляемых переменных в Swift

Я пытаюсь что-то выяснить с новым языком Apple Swift. Допустим, я делал что-то вроде следующего в Objective-C. У меня есть свойства readonly, и они не могут быть изменены по отдельности. Однако, используя определенный метод, свойства изменяются логическим способом.

Я беру следующий пример, очень простые часы. Я бы написал это в 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

Для определенной цели мы не можем напрямую касаться секунд, минут и часов, и разрешено только увеличивать секунду за секундой, используя метод. Только этот метод может изменить значения, используя прием переменных экземпляра.

Поскольку в Swift таких вещей нет, я пытаюсь найти эквивалент. Если я сделаю это:

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

Это бы сработало, но любой мог напрямую изменить свойства.

Возможно, у меня уже был плохой дизайн в Objective-C, и поэтому потенциальный новый Swift эквивалент не имеет смысла. Если нет и если у кого-то есть ответ, буду очень признателен;)

Может быть, будущее Механизмы контроля доступа обещанные Apple - это ответ?

Спасибо!

118
Zaxonov

Просто добавьте префикс объявления свойства к private(set), вот так:

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

private сохраняет его локальным для исходного файла, в то время как internal сохраняет его локальным для модуля/проекта.

private(set) создает свойство read-only, в то время как private устанавливает оба значения, set и get в private.

340
Ethan

Есть два основных способа делать то, что вы хотите. Первый способ заключается в наличии частной собственности и публичного вычисляемого свойства, которое возвращает это свойство:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Но это может быть достигнуто другим, более коротким способом, как указано в разделе "Контроль доступа" книги "The Swift Language" :

public class Clock {

    public private(set) var hours = 0

}

Как примечание, вы ДОЛЖНЫ предоставить публичный инициализатор при объявлении публичного типа. Даже если вы предоставляете значения по умолчанию для всех свойств, init() должен быть явно определен как public:

public class Clock {

    public private(set) var hours = 0

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

}

Это также объясняется в том же разделе книги, когда речь идет об инициализаторах по умолчанию.

20
elitalon

Поскольку контроля доступа нет (это означает, что вы не можете заключить контракт на доступ, который отличается в зависимости от того, кто является вызывающим абонентом), вот что я сейчас сделаю:

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

Это просто добавляет некоторый уровень косвенности к прямому доступу к счетчику; другой класс может сделать часы, а затем получить доступ к их счетчику напрямую. Но идея - т.е. контракт, который мы пытаемся заключить, - это то, что другой класс должен использовать только свойства и методы верхнего уровня Clock. Мы не можем обеспечить этот контракт, но на самом деле это было практически невозможно реализовать в Objective-C.

5
matt

На самом деле контроль доступа (который еще не существует в Swift) не такой строгий, как вы можете подумать в Задаче C. Люди могут изменить ваши переменные только для чтения, если они действительно этого хотят. Они просто не делают этого с открытым интерфейсом класса.

Вы можете сделать что-то подобное в Swift (вырезать и вставить ваш код, плюс некоторые модификации, я не тестировал это):

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

это то же самое, что и в Objective C, за исключением того, что фактические сохраненные свойства видны в общедоступном интерфейсе.

В Swift вы также можете сделать что-то более интересное, что вы также можете сделать в Objective C, но, вероятно, это красивее в Swift (отредактировано в браузере, я не проверял это):

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