Jaki jest cel willSet i didSet w Swift?

Swift ma składnię deklaracji właściwości bardzo podobną do C#:

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

Ma jednak również działania willSet i didSet. Są one wywoływane odpowiednio przed i po wywołaniu settera. Jaki jest ich cel, biorąc pod uwagę, że możesz mieć ten sam kod wewnątrz setera?

Author: Honey, 2014-06-03

11 answers

Wydaje się, że czasami potrzebna jest właściwość, która ma automatyczne przechowywanie i pewne zachowanie, na przykład, aby powiadomić inne obiekty, że właściwość właśnie się zmieniła. Kiedy wszystko co masz to get/set, do przechowywania wartości potrzebne jest inne pole. Za pomocą willSet i didSet można podjąć działania, gdy wartość zostanie zmodyfikowana bez potrzeby stosowania innego pola. Na przykład w tym przykładzie:

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

myProperty wypisuje jej starą i nową wartość za każdym razem, gdy jest modyfikowana. Z just getters a setery, przydałoby mi się to zamiast:

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

Więc willSet i didSet reprezentują ekonomię złożoną z kilku linii i mniej szumów na liście pól.

 333
Author: zneak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-11 15:06:06

Rozumiem, że set I get są dla computed properties (no backing from stored properties )

jeśli wywodzisz się z Objective-C pamiętaj, że konwencje nazewnictwa uległy zmianie. W języku Swift zmienna Ivar lub instancja nosi nazwę stored property

Przykład 1 (właściwość tylko do odczytu) - z ostrzeżeniem:

var test : Int {
    get {
        return test
    }
}

Spowoduje to Ostrzeżenie, ponieważ spowoduje to rekurencyjne wywołanie funkcji (wywołanie getter siebie).Ostrzeżenie w tym przypadku to "próba modyfikacji' testu 'we własnym getterze".

Przykład 2. Warunkowy Odczyt/Zapis-z ostrzeżeniem

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

Podobny problem - nie możesz tego zrobić, ponieważ to rekurencyjnie wywołuje setter. Zauważ również, że ten kod nie będzie narzekał na brak inicjatorów, ponieważ nie ma przechowywanej właściwości do inicjalizacji.

Przykład 3. read/write computed property-with backing store

Oto wzór, który pozwala warunkowe ustawienie rzeczywistej przechowywanej właściwości

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

Uwaga rzeczywiste dane nazywa się _test (chociaż mogą to być dowolne dane lub kombinacja danych) Zwróć również uwagę na konieczność podania wartości początkowej (alternatywnie musisz użyć metody init), ponieważ _test jest w rzeczywistości zmienną instancji

Przykład 4. Using 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
        }
    }
}

Tutaj widzimy willSet i didSet przechwytujące zmianę w rzeczywistej przechowywanej właściwości. Jest to przydatne do wysyłania powiadomienia, synchronizacja itp... (patrz przykład poniżej)

Przykład 5. Konkretny Przykład-Kontener 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
        }
    }
}

Zwróć uwagę na użycie zarówno właściwości obliczeniowych, jak i przechowywanych. Użyłem obliczonej właściwości, aby zapobiec dwukrotnemu ustawieniu tej samej wartości(aby uniknąć złych rzeczy!); Używałem willSet i didSet do przekazywania powiadomień do kontrolerów ViewController (zobacz dokumentację UIViewController i informacje o kontenerach ViewController)

Mam nadzieję, że to pomoże i proszę niech ktoś krzyknie, jeśli gdzieś tu popełniłem błąd!

 152
Author: user3675131,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-09 03:58:45

Nazywamy je obserwatorami własności :

Obserwatorzy Nieruchomości obserwują i reagują na zmiany w nieruchomości wartość. Obserwatory właściwości są wywoływane za każdym razem, gdy wartość właściwości jest Ustaw, nawet jeśli nowa wartość jest taka sama jak bieżąca właściwość wartość.

Fragment Z: Apple Inc. "Język Programowania Swift."iBooks. https://itun.es/ca/jEUH0.l

Podejrzewam, że to po to, by pozwolić na rzeczy, które tradycyjnie robimy z KVO takie jak powiązanie danych z elementami interfejsu użytkownika lub wywołanie skutków ubocznych zmiany właściwości, wyzwalanie procesu synchronizacji, przetwarzanie w tle itp.

 19
Author: Sebastien Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-03 10:51:03

Uwaga

willSet i didSet obserwatory nie są wywoływane, gdy właściwość jest ustawiona w inicjalizatorze przed delegowaniem

 17
Author: Bartłomiej Semańczyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Możesz również użyć didSet, aby ustawić zmienną na inną wartość. Nie powoduje to ponownego wywołania obserwatora, zgodnie z instrukcją właściwości . Na przykład, jest to przydatne, gdy chcesz ograniczyć wartość jak poniżej:

let minValue = 1

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

value = -10 // value is minValue now.
 16
Author: knshn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-05 11:48:58

Wiele dobrze napisanych istniejących odpowiedzi dobrze pokrywa pytanie, ale wspomnę, w niektórych szczegółach, dodatek, który moim zdaniem jest wart pokrycia.


Obserwatorzy właściwości willSet i didSet mogą być używane do wywoływania delegatów, np. dla właściwości klasy, które są zawsze aktualizowane tylko przez interakcję użytkownika, ale tam, gdzie chcesz uniknąć wywołania delegata przy inicjalizacji obiektu.

Przytoczę Klaas-głosował komentarz do zaakceptowanej odpowiedzi:

WillSet i obserwatory didSet nie są wywoływane, gdy właściwość jest pierwsza zainicjowany. Są one wywoływane tylko wtedy, gdy wartość właściwości jest ustawiona poza kontekstem inicjalizacji.

Jest to dość schludne, ponieważ oznacza to, że np. Właściwość didSet jest dobrym wyborem punktu startowego dla wywołań zwrotnych i funkcji delegata, dla własnych klas niestandardowych.

Jako przykład rozważ jakiś niestandardowy obiekt kontrolny użytkownika, z pewną kluczową właściwością value (np. pozycja w kontrolce oceny), zaimplementowaną jako podklasa 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.
}

, po czym twoje funkcje delegata mogą być używane w, powiedzmy, jakimś kontrolerze widoku do obserwowania kluczowych zmian w modelu dla CustomViewController, podobnie jak używasz nieodłącznych funkcji delegata UITextFieldDelegate dla UITextField obiektów (np. textFieldDidEndEditing(...)).

W tym prostym przykładzie, użyj delegata wywołania zwrotnego z didSet właściwości class value, aby powiedzieć kontrolerowi widoku, że jeden z jego gniazd ma powiązaną aktualizację modelu:

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

Tutaj, value własność został zamknięty, ale ogólnie: w takich sytuacjach należy uważać, aby nie aktualizować właściwości {[5] } obiektu customUserControl w zakresie powiązanej funkcji delegata (tutaj: didChangeValue()) w kontrolerze widoku, inaczej skończy się to nieskończoną rekurencją.

 10
Author: dfrib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-08 13:14:09

Obserwatorzy willSet i didSet dla właściwości za każdym razem, gdy właściwość ma przypisaną nową wartość. Jest to prawdą, nawet jeśli nowa wartość jest taka sama jak bieżąca wartość.

I zauważ, że willSet potrzebuje nazwy parametru do obejścia, z drugiej strony didSet nie.

Obserwator didSet jest wywoływany po zaktualizowaniu wartości właściwości. Porównuje się do starej wartości. Jeśli całkowita liczba kroków została zwiększona, wyświetlany jest komunikat wskazujący, w jaki sposób podjęto wiele nowych kroków. obserwator didSet nie dostarcza niestandardowej nazwy parametru dla starej wartości, a zamiast niej używana jest domyślna nazwa oldValue.

 5
Author: Zigii Wong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-10 07:33:27

Getter i setter są czasami zbyt ciężkie do zaimplementowania tylko po to, aby obserwować zmiany wartości. Zwykle wymaga to dodatkowej tymczasowej obsługi zmiennych i dodatkowych kontroli, a będziesz chciał uniknąć nawet tych małych prac, jeśli napiszesz setki getterów i seterów. Te rzeczy są dla sytuacji.

 2
Author: eonil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-03 03:39:18

W twojej klasie bazowej, willSet i didSetsą dość reduntant , ponieważ możesz zamiast tego zdefiniować obliczoną właściwość (tj. get - I set - methods), która uzyskuje dostęp do _propertyVariable i wykonuje pożądane pre - i post - prosessing .

If, however , you override a class where the property is already prefined, wtedy willSet i didSetużyteczne i nie zbędne!

 2
Author: ragnarius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-23 22:55:57

Jedna rzecz, gdzie didSet jest naprawdę przydatna, to gdy używasz gniazd, aby dodać dodatkową konfigurację.

@IBOutlet weak var loginOrSignupButton: UIButton! {
  didSet {
        let title = NSLocalizedString("signup_required_button")
        loginOrSignupButton.setTitle(title, for: .normal)
        loginOrSignupButton.setTitle(title, for: .highlighted)
  }
 1
Author: orkoden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-22 14:19:14

Nie znam C#, ale z odrobiną zgadywania chyba rozumiem co

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

Robi. Wygląda bardzo podobnie do tego, co masz w Swift, ale to nie to samo: w Swift nie masz getFoo i setFoo. To nie jest mała różnica: oznacza to, że nie masz żadnej podstawowej pamięci masowej dla swojej wartości.

Swift ma zapisane i obliczone właściwości.

Właściwość obliczeniowa ma get i może mieć set (jeśli jest zapisywalna). Ale kod w getterze i setterze, jeśli muszą faktycznie przechowywać niektóre dane, muszą to zrobić w Inne właściwości. Nie ma nośnika.

Przechowywana właściwość, z drugiej strony, ma backing storage. Ale to nie mA get i set. Zamiast tego ma willSet i didSet, których można użyć do obserwowania zmian zmiennych i, ostatecznie, wyzwalania efektów ubocznych i / lub modyfikowania przechowywanej wartości. Nie masz willSet i didSet dla właściwości obliczeniowych i nie potrzebujesz ich, ponieważ dla obliczeń właściwości możesz użyć kodu set do kontrolowania zmian.

 -5
Author: Analog File,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-05 08:08:42