Подтвердить что ты не робот

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

Вопрос

Apple docs указывают, что:

Наблюдатели

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 всегда будет вызываться. Я не решил, насколько это лучше в целом, но это хорошо подходит для моей ситуации.

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Если вы используете отсрочку внутри инициализатора, для обновления каких-либо дополнительных свойств или дальнейшего обновления необязательных свойств, которые вы уже инициализировали, и после того, как вы вызвали какие-либо методы супер-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

Ответ 3

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

class Classy {

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

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

}

Редактировать: ответ Брайана Вестфэла более приятный imho. Самое приятное в том, что он намекает на намерение.

Ответ 4

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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 действительно заботился об этом

Ответ 7

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

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

Обратите внимание, что решение Charlesism с закрытием всегда будет работать и в этом случае. Поэтому мое решение - это просто альтернатива.

Ответ 8

Вы можете решить это с помощью 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
    }
}