Властивості змінної лише для читання та не обчислені в 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

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

Оскільки у Свіфта таких речей немає, я намагаюся знайти еквівалент. Якщо я це роблю:

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, є відповіддю?

Дякую!


2
Контроль доступу - це відповідь. Ви можете створити лише обчислювані властивості, а приватні властивості використовувати для фактичних даних.
Лев Натан

Не забудьте відкрити звіт про помилку (удосконалення), щоб повідомити Apple, наскільки прямо цього немає.
Лев Натан

1
Для нових користувачів: Прокрутіть донизу ...
Gustaf Rosenblad

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 робить як налаштування, так і отримання функцій приватними
user965972

3
До цієї відповіді слід додати перший коментар, щоб зробити його більш повним.
Роб Норбак

Зараз (Swift 3.0.1) Рівень контролю доступу змінено: до "декларації файлів" можна отримати доступ лише за кодом у тому самому вихідному файлі, що і декларація ". Доступ до "приватної" декларації можна отримати лише за допомогою коду, що знаходиться в безпосередній області застосування.
Юрасич

20

Є два основні способи робити те, що ви хочете. Перший спосіб - приватна власність та публічна обчислена власність, яка повертає цю власність:

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

}

Це також пояснюється в тому ж розділі книги, коли йдеться про ініціалізатори за замовчуванням.


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. Ми не можемо застосувати цей контракт, але насправді це було майже неможливо виконати в Objective-C.


Схоже, це змінилося відтоді, правда? developer.apple.com/swift/blog/?id=5
Dan Rosenstark

1
@ Упевнений, весь цей питання / відповідь був написаний задовго до того, як до Swift було додано контроль доступу. І Свіфт все ще розвивається, тому відповіді продовжуватимуть застарівати.
мат

2

Насправді контроль доступу (який ще не існує у 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, за винятком того, що фактичні збережені властивості видно у загальнодоступному інтерфейсі.

У швидкому варіанті ви також можете зробити щось цікавіше, що ви також можете зробити в Objective C, але це, мабуть, красивіше у швидкому (відредаговано в браузері, я не перевіряв):

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

"Люди можуть безпосередньо змінювати ваші змінні для читання", - що саме ви маєте на увазі? Як?
користувач1244109

Я мав на увазі ціль C. У Objective C у вас є динамічний доступ до всього про клас через API інтерфейсу Runtime Objective C.
Аналоговий файл

я якось зрозумів, що ти мав на увазі це для швидкого. Цікаво. Дякую
користувач1244109
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.