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


217

質問

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常に呼び出されることがわかります。これが全体的に良いかどうかはまだ決めていませんが、自分の状況によく合います。


「これが迅速に機能する方法を受け入れる」ことだけが正直に最善です。varステートメントにインライン初期化値を配置した場合、もちろん、それは「didSet」を呼び出しません。そのため、「init()」シチュエーションも「didSet」を呼び出してはなりません。それはすべて理にかなっています。
Fattie

@Logan質問自体が私の質問に答えました;)ありがとう!
AamirR

2
便利なイニシャライザを使用したい場合は、 deferconvenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }
DarekCieśla

回答:


100

独自の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
    }
}

somePropertytypeとして宣言することによりAnyObject!(暗黙的にアンラップされたオプション)、selfを使用せずに完全に初期化できます。 someProperty設定されにます。を呼び出すsetSomeProperty(someProperty)と、と同等のものを呼び出し ます self.setSomeProperty(someProperty)。通常、自分自身が完全に初期化されていないため、これを行うことはできません。以来 someProperty、初期化を必要とせず、あなたが自己に依存メソッドを呼び出している、スウィフトは、初期コンテキストを離れ、didSetが実行されます。


3
なぜこれがinitのself.someProperty = newValueと異なるのですか?機能するテストケースはありますか?
mmmmmm 2014

2
なぜそれが機能するのか分からない-しかしそれは機能する。init()メソッド内で新しい値を直接設定しても、didSet()は呼び出されません。ただし、指定されたメソッドsetSomeProperty()を使用すると呼び出されます。
オリバー

50
私はここで何が起こっているか知っていると思います。somePropertytype:(AnyObject!暗黙的にアンラップされたオプション)として宣言することにより、self設定せずに完全に初期化できsomePropertyます。を呼び出すsetSomeProperty(someProperty)と、と同等のものを呼び出しますself.setSomeProperty(someProperty)。通常、self完全に初期化されていないため、これを行うことはできません。以来someProperty、初期化を必要とせず、あなたが方法に依存するのを呼び出しているself、スウィフトは、初期コンテキストを出てdidSet実行されます。
Logan

3
実際のinit()以外で設定されているものは、didSetが呼び出されるように見えます。文字通り、設定init() { *HERE* }されていないものは、didSetが呼び出されます。この質問には最適ですが、2次関数を使用していくつかの値を設定すると、didSetが呼び出されます。そのセッター関数を再利用したい場合は、あまり良くありません。
WCByrne 2015年

4
@Markを真剣に考えて、常識やロジックをSwiftに適用しようとするのはばかげています。しかし、賛成票を信頼するか、自分で試すことができます。私はやっただけでうまくいきました。そして、はい、それはまったく意味がありません。
Dan Rosenstark 16

305

イニシャライザの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

6
生意気な小さなトリック、私はそれが好きです...でもスウィフトの奇妙な癖。
Chris Hatton

4
すごい。を使用する別の便利なケースを見つけましたdefer。ありがとう。
ライアン

1
延期の活用!私はあなたに複数の賛成票を与えることを望みます。
クリスチャンシュノール2017年

3
非常に興味深い..このように使用される遅延をこれまでに見たことがない。それは雄弁であり、機能します。
aBikis 2017年

ただし、myOptionalFieldを2回設定すると、パフォーマンスの観点からはあまり良くありません。重い計算が行われた場合はどうなりますか?
ヤキフコヴァルスキー

77

オリバーの答えのバリエーションとして、行をクロージャーで囲むことができます。例えば:

class Classy {

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

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

}

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


2
これは賢い方法であり、冗長なメソッドでクラスを汚染することはありません。
pointum

ありがとうございます。Swift 2.2では、クロージャーを常に括弧で囲む必要があることに注意してください。
マックス

@Max Cheers、サンプルには現在括弧が含まれています
original_username

2
賢い答え!注:クラスが他のサブクラスである場合は、({self.foo = foo})()の前にsuper.init()を呼び出す必要があります
kcstricks

1
@Hlung、私はそれが将来何より壊れる可能性が高いとは感じていませんが、コードにコメントしないとコードが何をするのか非常にはっきりしないという問題がまだあります。少なくともブライアンの「据え置き」の回答は、初期化後にコードを実行したいというヒントを読者に与えます。
original_username

10

私は同じ問題を抱えていましたが、これは私にとってはうまくいきます

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

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

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

どこで手に入れたの?
Vanya

3
このアプローチは機能しなくなりました。コンパイラーは2つのエラーをスローします。–すべての保存されたプロパティが初期化される前にメソッド呼び出し '$ defer'で使用される 'self' –すべての保存されたプロパティを初期化せずにイニシャライザから戻る
Edward B

あなたは正しいですが、回避策があります。失敗を回避するために、somePropertyを暗黙のラップ解除またはオプションとして宣言し、デフォルト値をnilにまとめて提供するだけで済みます。tldr; somePropertyはオプションのタイプである必要があります
マーク

2

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

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


1

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

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

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

1
このソリューションでは、someProperty初期化中に値を指定しないため、オプションを変更する必要があります。
ミックマッカラム2018年

1

呼び出したい特定の場合 willSetスーパークラスで使用可能なプロパティまたはdidSet内部initで使用、スーパープロパティを直接割り当てるだけです。

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

なお、Charlesism閉鎖とソリューションは、常にそのような場合には、あまりにも動作します。だから私の解決策は単なる代替手段です。


-1

あなたはそれを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
    }
}

1
こんにちは、@ yshilovさん。これは、技術的に、self.someProperty初期化スコープを離れようとしていると私が望んでいた問題を実際に解決するとは思いません。同じことを行うことで達成できると思いvar someProperty: AnyObject! = nil { didSet { ... } }ます。それでも、追加のおかげで回避策として感謝する人もいるでしょう
Logan

@ローガンいや、それは動作しません。didSetはinit()では完全に無視されます。
yshilov、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.