Можно ли разрешить, чтобы didSet вызывался во время инициализации в Swift?

Вопрос

Документы Apple указывают, что:

Наблюдатели willSet и didSet не вызываются при первой инициализации свойства. Они вызываются только тогда, когда значение свойства установлено вне контекста инициализации.

Можно ли заставить их вызываться во время инициализации?

Зачем?

Допустим, у меня есть этот класс

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Я создал метод doStuff, чтобы сделать обработку вызовов более краткой, но я бы предпочел просто обработать свойство внутри didSet функция. Есть ли способ заставить это вызвать во время инициализации?

Обновить

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

8 ответов

Решение

Создайте собственный метод set и используйте его в своем методе init:

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }

    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }

    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

Если вы используете defer внутри инициализатора, для обновления любых необязательных свойств или дальнейшего обновления не необязательных свойств, которые вы уже инициализировали и после того, как вы вызвали любой super.init() методы, то ваш willSet, didSetи т. д. будет называться. Я считаю, что это удобнее, чем реализовывать отдельные методы, которые вы должны отслеживать в нужных местах.

Например:

public class MyNewType: NSObject {

    public var myRequiredField:Int

    public var myOptionalField:Float? {
        willSet {
            if let newValue = newValue {
                print("I'm going to change to \(newValue)")
            }
        }
        didSet {
            if let myOptionalField = self.myOptionalField {
                print("Now I'm \(myOptionalField)")
            }
        }
    }

    override public init() {
        self.myRequiredField = 1

        super.init()

        // Non-defered
        self.myOptionalField = 6.28

        // Defered
        defer {
            self.myOptionalField = 3.14
        }
    }
}

Даст:

I'm going to change to 3.14
Now I'm 3.14

Как вариант ответа Оливера, вы можете заключить строки в замыкание. Например:

class Classy {

    var foo: Int! { didSet { doStuff() } }

    init( foo: Int ) {
        // closure invokes didSet
        ({ self.foo = foo })()
    }

}

Редактировать: ответ Брайана Вестфаля лучше имхо. Самое приятное в том, что он намекает на намерение.

У меня была такая же проблема, и это работает для меня

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            doStuff()
        }
    }

    init(someProperty: AnyObject) {
        defer { self.someProperty = someProperty }
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}

Это работает, если вы делаете это в подклассе

class Base {

  var someProperty: AnyObject {
    didSet {
      doStuff()
    }
  }

  required init() {
    someProperty = "hello"
  }

  func doStuff() {
    print(someProperty)
  }
}

class SomeClass: Base {

  required init() {
    super.init()

    someProperty = "hello"
  }
}

let a = Base()
let b = SomeClass()

В a пример, didSet не срабатывает. Но в b пример, didSet срабатывает, потому что он находится в подклассе. Это связано с чем-то initialization context на самом деле означает, что в этом случае superclass действительно заботился об этом

Хотя это не решение проблемы, альтернативный способ сделать это - использовать конструктор класса:

class SomeClass {
    var someProperty: AnyObject {
        didSet {
            // do stuff
        }
    }

    class func createInstance(someProperty: AnyObject) -> SomeClass {
        let instance = SomeClass() 
        instance.someProperty = someProperty
        return instance
    }  
}

В частном случае, когда вы хотите вызвать willSet или же didSet внутри init для свойства, доступного в вашем суперклассе, вы можете просто назначить ваше супер-свойство напрямую:

override init(frame: CGRect) {
    super.init(frame: frame)
    // this will call `willSet` and `didSet`
    someProperty = super.someProperty
}

Обратите внимание, что в этом случае тоже будет работать решение с закрытием по принципу Шарлизма. Так что мое решение - просто альтернатива.

К сожалению, наблюдатели didSet не вызываются при инициализации корневого класса.

Если ваш класс не является подклассом, вы должны использовать геттеры и сеттеры для достижения желаемой функциональности:

class SomeClass {
    private var _test: Int = 0
    var test: Int {
        get { _test }
        set { _test = newValue }
    }
    
    init(test: Int) { self.test = test }
}

в качестве альтернативы, если ваш класс является подклассом, вы можете использовать didSet и делать:

override init(test: int) {
    super.init()
    self.test = test
}

DidSet ДОЛЖЕН вызываться после вызова super.init().

Одна вещь, которую я не пробовал, но МОЖЕТ тоже работать:

init(test: int) {
    defer { self.test = test }
}

ПРИМЕЧАНИЕ: вам нужно будет сделать ваши свойства допускающими значение NULL, или установить для них значение по умолчанию, или развернуть свойства класса.

Вы можете решить это в obj-с способом:

class SomeClass {
    private var _someProperty: AnyObject!
    var someProperty: AnyObject{
        get{
            return _someProperty
        }
        set{
            _someProperty = newValue
            doStuff()
        }
    }
    init(someProperty: AnyObject) {
        self.someProperty = someProperty
        doStuff()
    }

    func doStuff() {
        // do stuff now that someProperty is set
    }
}
Другие вопросы по тегам