Запустить новый ленивый инициализатор в Swift, установив свойство на nil

Я хочу lazily-initialized свойство, инициализатор которого я могу вызвать снова, если я установил свойство в nil.

Если я определяю свое свойство следующим образом:

lazy var object = { /*init code*/ }()

... и позже вызывать свойство, инициализатор запускается один раз. Однако, если я установил object в nil позже в моей программе, инициализатор не будет снова вызван. Как это сделать в Swift?

Я просмотрел вычисляемые свойства, но на самом деле они не хранят значения, поэтому всякий раз, когда я вызываю переменную, всегда происходит вычисление или инициализация. Я хочу вычислять только тогда, когда свойство равно nil.

Ответ 1

Инициализатор ленивых свойств отвечает за инициализацию свойства при первом доступе в режиме чтения. Настройка на nil не влияет на статус инициализации - это просто допустимое значение, которое сохраняет свойство.

Вы можете имитировать ленивую инициализацию с тремя свойствами:

  • частный инициализатор, реализованный как вычисленное свойство (или закрытие, если вы предпочитаете)
  • свойство частной поддержки, сохраняющее фактическое значение
  • не частное свойство, которое вы фактически используете в своем коде

Код выглядит следующим образом:

class MyClass {
    private var _myPropInitializer: Int {
        return 5
    }

    private var _myProp: Int?

    var myProp: Int? {
        get {
            if self._myProp == nil {
                self._myProp = self._myPropInitializer
            }
            return _myProp!
        }
        set {
            _myProp = newValue
        }
    }
}
  • свойство initializer возвращает вычисленное значение для переменной, когда оно должно быть инициализировано, что является 5 целым числом в приведенном выше примере
  • myProp - необязательное целое число (для хранения nil):
    • в наборе, он сохранит новое значение в свойстве _myProp
    • on get, если _myProp - nil, он вызывает инициализатор, присваивая его _myProp и возвращает его значение

Если вы хотите повторно использовать этот шаблон, лучше поместить все в класс:

class Lazy<T> {
    private let _initializer: () -> T
    private var _value: T?
    var value: T? {
        get {
            if self._value == nil {
                self._value = self._initializer()
            }
            return self._value
        }
        set {
            self._value = newValue
        }
    }

    required init(initializer: () -> T) {
        self._initializer = initializer
    }
}

Примечание: a struct неприменим, поскольку установка свойства внутри свойства getter недопустима, тогда как в классе это.

Затем вы можете использовать его следующим образом:

class MyTestClass {
    var lazyProp: Lazy<Int>

    init() {
        self.lazyProp = Lazy( { return 5 } )
    }
}

Некоторые тесты на детской площадке:

var x = MyTestClass()
x.lazyProp.value // Prints {Some 5}
x.lazyProp.value = nil
x.lazyProp._value // Prints nil
x.lazyProp.value // Prints {Some 5}

Недостатком является то, что вам нужно получить доступ к фактическому свойству как x.lazyProp.value, а не как x.lazyProp.

Ответ 2

Здесь ленивый шаблон, который я использую, когда ваш объект может быть только nil или вычисленное значение. Для этого требуется только 2 свойства:

var todayPredicate: NSPredicate! {
    set {
        guard newValue == nil else {return} // could throw here if required
        self._todayPredicateLazyBacker = nil
    }
    get {
        guard self._todayPredicateLazyBacker == nil else {return self. _todayPredicateLazyBacker}

        // construct your today predicate
        let predicate = ...

        self._todayPredicateLazyBacker = predicate
        return self._todayPredicateLazyBacker
    }
}
private var _todayPredicateLazyBacker: NSPredicate?

todayPredicate создается только один раз, когда он читается впервые (ленивый).

Итак, почему вы хотите установить todayPredicate в nil? В этом примере вы, вероятно, наблюдаете за изменением дня, потому что todayPredicate должен всегда отображаться сегодня. В вашем коде наблюдателя вы просто сделаете это, например...

self.todayPredicate = nil
self.loadEvents()