web-dev-qa-db-ja.com

Swiftの初期化中にdidSetを呼び出すことを許可することは可能ですか?

質問

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関数内のプロパティを処理するだけです。初期化中にこれを強制的に呼び出す方法はありますか?

更新

クラスの便利な初期化子を削除して、初期化後にプロパティを設定するように強制することにしました。これにより、didSetが常に呼び出されることを知ることができます。全体的にこれが良いかどうかは決めていませんが、私の状況によく合っています。

182
Logan

独自のset-Methodを作成し、init-Method内で使用します。

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

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

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

初期化子内でdeferを使用する場合、任意のプロパティを更新するため、またはすでに初期化しており、super.init()メソッドを呼び出した後の非オプションのプロパティを更新する場合、willSetdidSet、などが呼び出されます。これは、適切な場所で呼び出しを追跡する必要がある個別のメソッドを実装するよりも便利だと思います。

例えば:

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
241
Brian Westphal

Oliverの回答のバリエーションとして、クロージャーでラインをラップすることができます。例えば:

class Classy {

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

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

}

編集:ブライアン・ウェストファールの答えは私より良いです。彼のいいところは、それが意図を暗示していることです。

71
Charlesism

私は同じ問題を抱えており、これは私のために働く

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

これは、サブクラスでこれを行うと機能します

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はそれを気にしました

2
onmyway133

スーパークラスで使用可能なプロパティのwillSet内でdidSetまたはinitを呼び出す特定のケースでは、スーパープロパティを直接割り当てることができます。

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

Charlesism クロージャーを使用したソリューションは、その場合でも常に機能することに注意してください。したがって、私のソリューションは単なる代替です。

1
Cœur

これは解決策ではありませんが、別の方法でクラスコンストラクターを使用することもできます。

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

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