Только для чтения и не вычисляемые переменные свойства в 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, является ответом?

Спасибо!

Ответ 1

Просто префикс объявления свойства с помощью 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.

Ответ 2

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

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

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

public class Clock {

    public private(set) var hours = 0

}

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

public class Clock {

    public private(set) var hours = 0

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

}

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

Ответ 3

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

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.

Ответ 4

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

Вы можете сделать что-то подобное в 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, за исключением того, что фактические сохраненные свойства видны в открытом интерфейсе.

В быстром вы также можете сделать что-то более интересное, что вы также можете сделать в 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++
    }
}