Можно ли разрешить, чтобы 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
}
}