Swift에서 willSet 및 didSet의 목적은 무엇입니까?


265

Swift에는 C #과 매우 유사한 속성 선언 구문이 있습니다.

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

그러나, 그것은 또한 가지고 willSetdidSet행동. 이들은 각각 setter가 호출되기 전후에 호출됩니다. 세터 내에서 동일한 코드를 가질 수 있다고 생각하면 그 목적은 무엇입니까?


11
나는 개인적으로 여기에 많은 답변을 좋아하지 않습니다. 구문에 너무 많이 들어가 있습니다. 차이점은 의미론 및 코드 준비성에 관한 것입니다. 컴퓨팅 속성 ( get& set)은 기본적으로 다른 속성을 기반으로 속성을 계산 하는 것입니다 (예 : 레이블 text을 1 년으로 변환) Int. didSet& willSet할 말이 있습니다 ...이 값이 설정되었습니다. 이제이 작업을 수행하십시오. 예를 들어 dataSource가 업데이트되었습니다 ... 테이블을 다시로드하여 새 행을 포함시킵니다. 다른 예를 들어 – 델리의 전화를 거는 방법에 대한 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
이니셜 라이저 내에서 연기 {} 문으로 할당을 래핑하여 이니셜 라이저 범위가 종료 될 때 willSet 및 didSet 메소드가 호출되도록 할 수 있습니다. 나는 그것이 가능하다고 말하면서 반드시 그것을 추천하지는 않습니다. 그 결과 중 하나는 이니셜 라이저에서 엄격하게 초기화되지 않기 때문에 선택적 속성을 선언 한 경우에만 작동한다는 것입니다.
Marmoy

아래 줄을 설명하십시오. 이 메소드 또는 변수가 아닙니다. var propertyChangedListener : (Int, Int)-> Void = {println ( "myProperty의 값이 ($ 0)에서 ($ 1) (으)로 변경되었습니다.")}
Vikash Rajput

Swift 3에서는 동일한 줄에서 속성을 초기화 할 수 없습니다. 신속한 3을 따르도록 답변을 변경해야합니다.
Ramazan Polat

149

내 이해는 set과 get이 계산 된 속성을 위한 것입니다 ( 저장 된 속성 에서지지 않음 )

Objective-C에서 온 경우 명명 규칙이 변경되었음을 염두에 두어야합니다. Swift에서 iVar 또는 인스턴스 변수의 이름은 저장 속성입니다

예제 1 (읽기 전용 속성)-경고 :

var test : Int {
    get {
        return 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
        }
    }
}

비슷한 문제- 재귀 적으로 setter를 호출 하므로이 작업을 수행 할 수 없습니다 . 또한 초기화 할 저장된 속성이 없기 때문에이 코드는 이니셜 라이저에 대해 불평하지 않습니다 .

예 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 and did set 사용

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

계산 및 저장된 속성을 모두 사용하십시오. 나는 계산 된 속성을 사용하여 같은 값을 두 번 설정하지 못하게합니다 (나쁜 일이 발생하지 않도록!); willSet 및 didSet을 사용하여 viewController에 알림을 전달했습니다 (viewController 컨테이너에 대한 UIViewController 설명서 및 정보 참조).

나는 이것이 도움이되기를 희망하며, 여기 어디에서나 실수를했다면 누군가가 소리 지르십시오!


3
왜 get 및 set과 함께 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) {
사라짐

5
get 및 set은 계산 된 속성을 만드는 데 사용됩니다. 이들은 순전히 방법이며 백업 저장 공간이 없습니다 (인스턴스 변수). willSet 및 didSet은 저장된 변수 속성의 변경 사항을 관찰하기위한 것입니다. 후드 아래에는 스토리지가 지원되지만 Swift에서는 모두 하나로 통합되어 있습니다.
user3675131

귀하의 예 5에서에 get추가 if _childVC == nil { _childVC = something }한 다음 을 추가해야한다고 생각합니다 return _childVC.
JW.ZG 2016 년

18

이를 속성 관찰자 라고합니다 .

부동산 관찰자는 부동산 가치의 변화를 관찰하고 이에 대응합니다. 새 값이 속성의 현재 값과 동일하더라도 속성 값이 설정 될 때마다 속성 옵저버가 호출됩니다.

발췌 : Apple Inc.“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다음 UITextFieldDelegatefor UITextField객체 (예 :)의 고유 한 델리게이트 함수를 사용하는 것처럼 델리게이트 함수를 사용하여 모델의 주요 변경 사항을 관찰하기 위해 일부 뷰 컨트롤러에 사용할 수 있습니다 textFieldDidEndEditing(...).

이 간단한 예제 didSet의 경우, 클래스 속성의 델리게이트 콜백을 사용하여 value뷰 컨트롤러의 콘센트 중 하나에 연결된 모델 업데이트가 있음을 뷰 컨트롤러에 알립니다.

// 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에서 customUserControl객체 의 속성 을 업데이트하지 않도록주의 하십시오. didChangeValue()무한 재귀.


4

속성에 새로운 값이 할당 될 때마다 속성에 대한 willSet 및 didSet 옵저버. 새 값이 현재 값과 동일한 경우에도 마찬가지입니다.

willSet반면에 해결하려면 매개 변수 이름 이 필요 didSet하지 않습니다.

속성 값이 업데이트 된 후 didSet 옵저버가 호출됩니다. 이전 값과 비교합니다. 총 단계 수가 증가한 경우 새 단계 수를 나타내는 메시지가 인쇄됩니다. didSet 옵저버는 이전 값에 대한 사용자 정의 매개 변수 이름을 제공하지 않으며 기본 이름 인 oldValue가 대신 사용됩니다.


2

Getter와 Setter는 때때로 적절한 값 변경을 관찰하기에는 구현하기에 너무 무겁습니다. 일반적으로 여기에는 추가 임시 변수 처리 및 추가 검사가 필요하며 수백 개의 게터와 세터를 작성하는 경우에도 이러한 작은 노력조차 피할 수 있습니다. 이런 것들이 상황에 대한 것입니다.


1
동등한 세터 코드 를 사용하는 것과 비교 하여 성능상의 이점 이 있다고 말하고 있습니까? 이것은 대담한 주장처럼 보입니다. willSetdidSet
zneak

1
@zneak 잘못된 단어를 사용했습니다. 처리 비용이 아닌 프로그래머의 노력을 주장하고 있습니다.
Eonil

1

자신의 (기본) 클래스에 willSet있고 , 원하는 사전 및 사후 처리에 액세스 하고 수행 하는 계산 된 속성 (예 : get 및 set 메소드)을 대신 정의 할 수 didSet있으므로 상당히 중복 됩니다 ._propertyVariable

, 경우 그러나 , 당신은 속성이되는 클래스 오버라이드 (override) 이미 정의를 , 다음willSet 하고 didSet있습니다 유용하고 중복되지!


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을 사용하면이 콘센트 메소드에 어떤 영향을 줄 수 있습니까?
elia

-5

나는 C #을 모른다. 그러나 약간의 추측으로 나는 무엇을 이해한다고 생각한다.

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

그렇습니다. Swift에있는 것과 매우 유사 해 보이지만 동일하지 않습니다 . Swift에는 getFooand 가 없습니다 setFoo. 이는 약간의 차이가 아닙니다. 이는 가치를위한 기본 스토리지가 없음을 의미합니다.

Swift는 속성을 저장하고 계산했습니다.

계산 된 속성은 (쓰기 가능하다면) get가지고있을 수도 있습니다 set. 그러나 게터 및 세터의 코드는 실제로 일부 데이터를 저장해야하는 경우 다른 속성 에서 수행해야합니다 . 백업 스토리지가 없습니다.

반면에 저장된 속성에는 백업 저장소가 있습니다. 그러나 않습니다 하지getset. 대신은이 willSetdidSet어떤 당신은 변수의 변화와, 결국, 트리거 부작용을 관찰하는 데 사용 및 / 또는 저장된 값을 수정할 수 있습니다. 당신이없는 willSetdidSet계산 된 속성 및 계산 된 속성에 대한 당신의 코드를 사용할 수 있기 때문에 당신이 그들을 필요가 없습니다 set제어로 바뀝니다.


이것은 스위프트 예제입니다. getFoo그리고 setFoo당신은 getter와 할 세터 싶습니다 무엇 이건 간단한 자리입니다. C #도 필요하지 않습니다. (컴파일러에 액세스하기 전에 요청한대로 구문 미묘한 부분을
놓쳤습니다

1
오 그래. 그러나 중요한 점은 계산 된 속성에 기본 저장소가 없다는 것입니다. 내 다른 답변을 참조하십시오 : stackoverflow.com/a/24052566/574590
Analog File
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.