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

126

Пытаюсь что-то придумать с новым языком 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 будущие механизмы контроля доступа станут ответом?

Спасибо!

Zaxonov
источник
2
Контроль доступа - вот ответ. Вы должны создать вычисляемые свойства только для чтения, а для фактических данных использовать частные свойства.
Leo Natan
Обязательно откройте отчет об ошибке (улучшение), чтобы сообщить Apple, насколько сильно это отсутствует.
Leo Natan
1
Для новых пользователей: прокрутите вниз ...
Густав Розенблад
2
Пожалуйста, отметьте ответ @ Ethan как правильный.
phatmann

Ответы:

356

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

Итан
источник
28
"частный", чтобы сохранить его локальным по отношению к исходному файлу, "внутренний", чтобы сохранить его локальным по отношению к модулю / проекту. private (set) делает только набор закрытым. Private делает функции set и get закрытыми
user965972
3
К этому ответу следует добавить первый комментарий, чтобы сделать его более полным.
Роб Норбак
Прямо сейчас (Swift 3.0.1) Уровни контроля доступа изменены: к объявлению "fileprivate" можно получить доступ только с помощью кода в том же исходном файле, что и объявление ». Доступ к объявлению "private" можно получить только с помощью кода в пределах непосредственной области действия объявления.
Jurasic
20

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

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

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

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

}

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

elitalon
источник
5

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

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, а затем напрямую обращаться к его счетчику. Но идея - то есть контракт, который мы пытаемся заключить - состоит в том, что другой класс должен использовать только свойства и методы верхнего уровня Clock. Мы не можем обеспечить соблюдение этого контракта, но на самом деле это было практически невозможно и в Objective-C.

матовый
источник
Похоже, с тех пор все изменилось, верно? developer.apple.com/swift/blog/?id=5
Дэн Розенстарк
1
@Yar конечно, весь этот вопрос / ответ был написан задолго до того, как контроль доступа был добавлен в Swift. А Swift все еще развивается, поэтому ответы будут устаревать.
Мэтт
2

На самом деле контроль доступа (который еще не существует в Swift) не так строг, как вы думаете в Objective 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++
    }
}
Аналоговый файл
источник
«Люди могут напрямую изменять ваши переменные только для чтения» - что именно вы имеете в виду? Как?
user1244109
Я имел в виду цель C. В Objective C у вас есть динамический доступ ко всему, что касается класса, через API среды выполнения Objective C.
Аналоговый файл
Я как-то понял, что вы подразумевали это для быстрого. Стало любопытно. Спасибо
user1244109