SwiftのwillSetとdidSetの目的は何ですか?


265

Swiftには、C#とよく似たプロパティ宣言構文があります。

var foo: Int {
    get { return getFoo() }
    set { setFoo(newValue) }
}

しかし、それはまた、持っているwillSetdidSetアクション。これらは、セッターが呼び出される前と後にそれぞれ呼び出されます。セッター内に同じコードを含めることができると考えると、それらの目的は何ですか?


11
私は個人的にはここで多くの答えが好きではありません。それらは構文に深く入りすぎています。違いは、セマンティクスとコードの可読性についてです。計算されたプロパティ(getset)は、基本的に、別のプロパティに基づいて計算されたプロパティを持つことです。たとえば、ラベルをtext年に変換しIntます。 didSetwillSet言うことはあります...この値が設定されたので、これを実行しましょう。たとえば、dataSourceが更新されたので、tableViewをリロードして新しい行を含めます。別の例については、代理人を呼び出す方法に関するdfriの回答をdidSet
Honey

回答:


324

重要なのは、時々、たとえば、プロパティが変更されたことを他のオブジェクトに通知するために、自動ストレージいくつかの動作を備えたプロパティが必要になることです。get/ だけの場合はset、値を保持する別のフィールドが必要です。willSetおよびを使用するとdidSet、別のフィールドを必要とせずに値が変更されたときにアクションを実行できます。たとえば、その例では:

class Foo {
    var myProperty: Int = 0 {
        didSet {
            print("The value of myProperty changed from \(oldValue) to \(myProperty)")
        }
    }
}

myProperty変更されるたびに、古い値と新しい値を出力します。ゲッターとセッターだけで、代わりにこれが必要になります:

class Foo {
    var myPropertyValue: Int = 0
    var myProperty: Int {
        get { return myPropertyValue }
        set {
            print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
            myPropertyValue = newValue
        }
    }
}

したがってwillSetdidSet数行の経済性を表し、フィールドリストのノイズを減らします。


248
注意:willSetそしてdidSetあなたはアップルのノートとしてinitメソッド内からプロパティを設定するときに呼び出されていない:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
クラース

4
ただし、これを行うと、配列プロパティで呼び出されるようですmyArrayProperty.removeAtIndex(myIndex)。...予期されていません。
Andreas

4
初期化子内のdefer {}ステートメントで割り当てをラップすると、初期化子スコープが終了したときにwillSetメソッドとdidSetメソッドが呼び出されます。私は必ずしもそれを推奨しているわけではなく、可能だと言っているだけです。結果の1つは、プロパティをオプションとして宣言した場合にのみ機能することです。これは、厳密には初期化子から初期化されていないためです。
マルモイ

以下の行を説明してください。取得できません。このメソッドまたは変数はvar propertyChangedListener:(Int、Int)-> Void = {println( "myPropertyの値が($ 0)から($ 1)に変更されました)}
Vikash Rajput

同じラインでの初期化プロパティは、あなたが迅速3.準拠するように、答えを変更する必要がありスウィフト3でサポートされていません
ラマザンポラット

149

私の理解では、設定と取得は計算されたプロパティ用です保存されたプロパティからのバッキングはありません)

Objective-Cを使用している場合は、命名規則が変更されたことを念頭に置いてください。Swiftでは、iVarまたはインスタンス変数はstoredプロパティと呼ばれます

例1(読み取り専用プロパティ)-警告あり:

var test : Int {
    get {
        return test
    }
}

これにより、再帰的な関数呼び出し(ゲッター自体が呼び出される)になるため、警告が表示されます。この場合の警告は、「独自のゲッター内で「test」を変更しようとしています」です。

例2.条件付き読み取り/書き込み-警告あり

var test : Int {
    get {
        return test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        //(prevents same value being set)
        if (aNewValue != test) {
            test = aNewValue
        }
    }
}

同様の問題- 再帰的にセッターを呼び出すため、これ行うことはできません。また、このコードは、初期化する格納されたプロパティがないため、初期化子がないことについて不平を言わないことに注意してください。

例3.計算プロパティの読み取り/書き込み-バッキングストアを使用

これは、実際に保存されたプロパティの条件付き設定を可能にするパターンです

//True model data
var _test : Int = 0

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

実際のデータは_testと呼ばれます(任意のデータまたはデータの組み合わせである可能性があります)また、_testは実際にはインスタンス変数であるため、初期値を提供する必要があります(または、initメソッドを使用する必要があります)。

例4. willおよびdidセットの使用

//True model data
var _test : Int = 0 {

    //First this
    willSet {
        println("Old value is \(_test), new value is \(newValue)")
    }

    //value is set

    //Finaly this
    didSet {
        println("Old value is \(oldValue), new value is \(_test)")
    }
}

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

ここでは、実際に保存されたプロパティの変更をインターセプトするwillSetとdidSetを確認します。これは、通知の送信、同期などに役立ちます(下の例を参照)。

例5.具体的な例-ViewControllerコンテナ

//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
    willSet {
        //REMOVE OLD VC
        println("Property will set")
        if (_childVC != nil) {
            _childVC!.willMoveToParentViewController(nil)
            self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
            _childVC!.view.removeFromSuperview()
            _childVC!.removeFromParentViewController()
        }
        if (newValue) {
            self.addChildViewController(newValue)
        }

    }

    //I can't see a way to 'stop' the value being set to the same controller - hence the computed property

    didSet {
        //ADD NEW VC
        println("Property did set")
        if (_childVC) {
//                var views  = NSDictionaryOfVariableBindings(self.view)    .. NOT YET SUPPORTED (NSDictionary bridging not yet available)

            //Add subviews + constraints
            _childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false)       //For now - until I add my own constraints
            self.view.addSubview(_childVC!.view)
            let views = ["view" : _childVC!.view] as NSMutableDictionary
            let layoutOpts = NSLayoutFormatOptions(0)
            let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|",  options: layoutOpts, metrics: NSDictionary(), views: views)
            let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
            self.view.addConstraints(lc1)
            self.view.addConstraints(lc2)

            //Forward messages to child
            _childVC!.didMoveToParentViewController(self)
        }
    }
}


//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
    get {
        return _childVC
    }
    set(suggestedVC) {
        if (suggestedVC != _childVC) {
            _childVC = suggestedVC
        }
    }
}

計算され保存されたプロパティの両方の使用に注意してください。計算されたプロパティを使用して、同じ値が2度設定されるのを防ぎました(悪いことの発生を避けるためです!)。willSetとdidSetを使用して、通知をviewControllersに転送しました(UIViewControllerのドキュメントとviewControllerコンテナの情報を参照してください)。

これがお役に立てば幸いです。ここで間違いを犯した場合は、誰かが叫んでください!


3
getSetと一緒にdidSetを使用できないのはなぜですか?
Ben Sinclair

//I can't see a way to 'stop' the value being set to the same controller - hence the computed propertyif let newViewController = _childVC { 代わり に私が使用した後の警告が消える if (_childVC) {
evfemist '20

5
getおよびsetは、計算されたプロパティを作成するために使用されます。これらは純粋にメソッドであり、バッキングストレージ(インスタンス変数)はありません。willSetとdidSetは、格納された変数プロパティへの変更を監視するためのものです。内部的にはこれらはストレージによってサポートされていますが、Swiftではすべて1つに統合されています。
user3675131 2015

例5では、でgetif _childVC == nil { _childVC = something }次にを追加する必要があると思いますreturn _childVC
JW.ZG 2016年

18

これらはプロパティオブザーバーと呼ばれます。

プロパティオブザーバーは、プロパティの値の変化を観察して応答します。プロパティオブザーバーは、新しい値がプロパティの現在の値と同じであっても、プロパティの値が設定されるたびに呼び出されます。

抜粋:Apple Inc.「The Swift Programming Language」iBooks。https://itun.es/ca/jEUH0.l

UI要素とのデータバインディング、プロパティ変更の副作用のトリガー、同期プロセスのトリガー、バックグラウンド処理など、KVOで従来から行っていたことが可能になると思います。



16

を使用しdidSetて、変数を別の値に設定することもできます。これにより、プロパティガイドに記載されているように、オブザーバーが再度呼び出されることはありません。たとえば、次のように値を制限する場合に便利です。

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.

10

よく書かれた多くの既存の回答がこの問題を十分にカバーしていますが、カバーする価値があると私が信じている追加について少し詳しく述べます。


財産オブザーバーは今までユーザーとの対話によって更新されているクラスのプロパティのために、例えば、デリゲートを呼び出すために使用することができますが、オブジェクトの初期化時にデリゲートを呼び出さないようにしたいところ。willSetdidSet

承認された回答に対するKlaasの投票コメントを引用します。

プロパティが最初に初期化されるとき、willSetおよびdidSetオブザーバーは呼び出されません。プロパティの値が初期化コンテキスト外で設定されている場合にのみ呼び出されます。

これは、たとえば、didSetプロパティが、独自のカスタムクラスのデリゲートコールバックと関数の起動ポイントの適切な選択であることを意味するため、非常に優れています。

例として、いくつかのキープロパティvalue(たとえば、評価コントロール内の位置)を持ち、のサブクラスとして実装されたカスタムユーザーコントロールオブジェクトを考えますUIView

// CustomUserControl.swift
protocol CustomUserControlDelegate {
    func didChangeValue(value: Int)
    // func didChangeValue(newValue: Int, oldValue: Int)
    // func didChangeValue(customUserControl: CustomUserControl)
    // ... other more sophisticated delegate functions
}

class CustomUserControl: UIView {

    // Properties
    // ...
    private var value = 0 {
        didSet {
            // Possibly do something ...

            // Call delegate.
            delegate?.didChangeValue(value)
            // delegate?.didChangeValue(value, oldValue: oldValue)
            // delegate?.didChangeValue(self)
        }
    }

    var delegate: CustomUserControlDelegate?

    // Initialization
    required init?(...) { 
        // Initialise something ...

        // E.g. 'value = 1' would not call didSet at this point
    }

    // ... some methods/actions associated with your user control.
}

これは後にデリゲート関数は、のためのモデルの重要な変化を観察するために、いくつかのビューコントローラ、たとえば、で使用することができCustomViewControllerますが、本来の委任機能を使用すると思いずっと同じように、UITextFieldDelegateのためにUITextFieldオブジェクト(例えばtextFieldDidEndEditing(...))。

この簡単な例でdidSetは、クラスプロパティのからのデリゲートコールバックを使用して、valueそのアウトレットの1つにモデルの更新が関連付けられていることをビューコントローラに通知します。

// ViewController.swift
Import UIKit
// ...

class ViewController: UIViewController, CustomUserControlDelegate {

    // Properties
    // ...
    @IBOutlet weak var customUserControl: CustomUserControl!

    override func viewDidLoad() {
        super.viewDidLoad()
        // ...

        // Custom user control, handle through delegate callbacks.
        customUserControl = self
    }

    // ...

    // CustomUserControlDelegate
    func didChangeValue(value: Int) {
        // do some stuff with 'value' ...
    }

    // func didChangeValue(newValue: Int, oldValue: Int) {
        // do some stuff with new as well as old 'value' ...
        // custom transitions? :)
    //}

    //func didChangeValue(customUserControl: CustomUserControl) {
    //    // Do more advanced stuff ...
    //}
}

ここでは、valueプロパティがカプセル化されていますが、一般に、このような状況では、ビューコントローラーの関連するデリゲート関数(ここでは)のスコープでオブジェクトのvalueプロパティを更新しないように注意してください。更新しないと、無限再帰。customUserControldidChangeValue()


4

プロパティに新しい値が割り当てられるたびに、プロパティのwillSetおよびdidSetオブザーバー。これは、新しい値が現在の値と同じであっても当てはまります。

またwillSet、回避するにはパラメーター名が必要ですが、回避する必要didSetはありません。

プロパティの値が更新された後に、didSetオブザーバーが呼び出されます。古い値と比較します。総ステップ数が増加した場合、新しいステップがいくつ行われたかを示すメッセージが出力されます。didSetオブザーバーは古い値のカスタムパラメーター名を提供せず、代わりにoldValueのデフォルト名が使用されます。


2

ゲッターとセッターは、適切な値の変化を観察するためだけに実装するには重すぎる場合があります。通常、これには追加の一時変数処理と追加のチェックが必要であり、何百ものゲッターとセッターを作成する場合は、これらの小さな労力でさえ回避する必要があります。これらは状況に応じたものです。


1
同等のセッターコードを使用した場合と同等の場合のパフォーマンス上の利点があるとおっしゃっていますか?これは大胆な主張のようです。willSetdidSet
zneak 2014年

1
@zneak間違った単語を使用しました。処理コストではなく、プログラマの努力を主張しています。
Eonil、2014年

1

独自の(基本)クラスでwillSetdidSet非常に冗長です。代わりに、にアクセス_propertyVariableして目的の前後処理を行う計算プロパティ(つまり、getメソッドとsetメソッド)を定義できます。

、場合しかし、あなたはプロパティがされたクラス上書きすでに定義しそれからwillSetdidSetしている便利な、冗長ではありません!


1

一つのことdidSetあなたは追加の設定を追加するためにコンセントを使用するときに本当に便利ですです。

@IBOutlet weak var loginOrSignupButton: UIButton! {
  didSet {
        let title = NSLocalizedString("signup_required_button")
        loginOrSignupButton.setTitle(title, for: .normal)
        loginOrSignupButton.setTitle(title, for: .highlighted)
  }

または、willSetを使用すると、このアウトレットメソッドにいくつかの効果がありますね。
エリア

-5

C#はわかりませんが、少し推測するだけで、

foo : int {
    get { return getFoo(); }
    set { setFoo(newValue); }
}

します。Swiftにあるものとよく似ていますが、同じではありません。SwiftにはgetFooand がありませんsetFoo。それは小さな違いではありません。つまり、価値のための基盤となるストレージがないということです。

Swiftはプロパティを保存および計算しました。

計算されたプロパティには(書き込み可能であれば)ありget、ある場合がありますset。ただし、ゲッターとセッターのコードは、実際にデータを格納する必要がある場合は、他のプロパティで実行する必要があります。バッキングストレージはありません。

一方、保存されたプロパティにはバッキングストレージがあります。しかし、それはないではない持っているgetset。代わりに、それは持っているwillSetdidSetあなたは変数の変化を観察して、最終的には、トリガーの副作用を、および/または格納された値を変更するために使用できます。計算されたプロパティはwillSetありません。またdidSet、計算されたプロパティではコードを使用してset変更を制御できるため、それらは必要ありません。


これはSwiftの例です。getFooそしてsetFoo、ゲッターとセッターに何をしてほしいのかという単純なプレースホルダーです。C#でも必要ありません。(コンパイラーにアクセスする前に尋ねたように、私はいくつかの構文上の微妙な部分を逃しました。)
zneak

1
ああ、わかりました。しかし重要な点は、計算されたプロパティには基礎となるストレージがないということです。私の他の回答もご覧ください:stackoverflow.com/a/24052566/574590
Analog File
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.