web-dev-qa-db-fra.com

Est-il possible de permettre à didSet d'être appelé lors de l'initialisation dans Swift?

Question

documentation d'Apple précisez que:

les observateurs willSet et didSet ne sont pas appelés lors de la première initialisation d'une propriété. Ils ne sont appelés que lorsque la valeur de la propriété est définie en dehors d’un contexte d’initialisation.

Est-il possible de forcer ces derniers à être appelés lors de l'initialisation?

Pourquoi?

Disons que j'ai ce cours

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

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

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

J'ai créé la méthode doStuff, pour rendre les appels de traitement plus concis, mais je préfère traiter la propriété dans la fonction didSet. Existe-t-il un moyen de forcer cet appel lors de l'initialisation?

Mise à jour

J'ai décidé de simplement supprimer l'initialisateur de commodité pour ma classe et de vous forcer à définir la propriété après l'initialisation. Cela me permet de savoir que didSet sera toujours appelé. Je n'ai pas encore décidé si cela est meilleur dans l'ensemble, mais cela convient bien à ma situation.

188
Logan

Créez votre propre méthode set et utilisez-la dans votre méthode init:

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

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

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

Si vous utilisez defer à l'intérieur d'un initialiseur, pour mettre à jour les propriétés facultatives ou pour mettre à jour les propriétés non facultatives que vous avez déjà initialisées et après que vous ayez appelé super.init() méthodes, puis votre willSet, didSet, etc. sera appelée. Je trouve cela plus pratique que de mettre en œuvre des méthodes distinctes qui vous obligent à garder la trace des appels aux bons endroits.

Par exemple:

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

Rendra:

I'm going to change to 3.14
Now I'm 3.14
259
Brian Westphal

En variante de la réponse d'Oliver, vous pouvez envelopper les lignes dans une fermeture. Par exemple:

class Classy {

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

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

}

Edit: la réponse de Brian Westphal est plus gentille à mon humble avis. La bonne chose à propos de son, c'est qu'il fait allusion à l'intention.

72
Charlesism

J'ai eu le même problème et cela fonctionne pour moi

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

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

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

Cela fonctionne si vous faites cela dans une sous-classe

_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()
_

Dans l'exemple a, didSet n'est pas déclenché. Mais dans l'exemple b, didSet est déclenché, car il se trouve dans la sous-classe. Cela doit faire quelque chose avec ce que _initialization context_ signifie réellement, dans ce cas, la superclass se souciait de ça

2
onmyway133

Dans le cas particulier où vous souhaitez invoquer willSet ou didSet à l'intérieur de init pour une propriété disponible dans votre superclasse, vous pouvez simplement affecter votre propriété super directement:

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

Notez que Charlesism une solution avec une fermeture fonctionnerait toujours aussi dans ce cas. Donc, ma solution est juste une alternative.

1
Cœur

Bien que cela ne soit pas une solution, une autre façon de procéder consiste à utiliser un constructeur de classe:

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

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