Swift'de willSet ve didSet'in amacı nedir?


265

Swift, C # 'lara çok benzer bir özellik bildirimi sözdizimine sahiptir:

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

Ancak, aynı zamanda willSetve didSeteylemleri vardır. Bunlar sırasıyla ayarlayıcının çağrılmasından önce ve sonra çağrılır. Seter içinde aynı koda sahip olabileceğiniz göz önüne alındığında, amaçları nedir?


11
Ben şahsen burada pek çok cevaptan hoşlanmıyorum. Sözdizimine çok fazla iniyorlar. Farklılıklar daha çok anlambilim ve kodların yeniden kullanılabilirliği ile ilgilidir. Hesaplanan Mülk ( get& set) temel olarak başka bir mülke dayanarak hesaplanan bir özelliğe sahip olmalıdır, örneğin bir etiketin textbir yıla dönüştürülmesi Int. didSet& willSetdemek ki var ... hey bu değer ayarlandı, şimdi bunu yapalım örneğin dataSource'umuz güncellendi ... öyleyse yeni satırları içerecek şekilde tableView'i yeniden yükleyelim. Başka bir örnek için dfri'nin delegeleri nasıl arayacağına dair cevabadidSet
Honey

Yanıtlar:


324

Mesele şu ki, bazen otomatik depolama ve bazı davranışları olan bir özelliğe ihtiyacınız var , örneğin mülkün yeni değiştiğini diğer nesnelere bildirmek için. Sahip olduğunuz tek şey get/ olduğunda set, değeri tutmak için başka bir alana ihtiyacınız vardır. İle willSetve didSetdeğer başka bir alana ihtiyaç duymadan modifiye edildiğinde, sipariş harekete geçebilir. Örneğin, bu örnekte:

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

myPropertyher değiştirilişinde eski ve yeni değerini yazdırır. Sadece alıcılar ve ayarlayıcılarla, bunun yerine buna ihtiyacım olurdu:

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

Bu yüzden willSetve didSetbirkaç satırlık bir ekonomiyi ve alan listesinde daha az gürültüyü temsil eder.


248
Dikkat: willSetve didSetözelliği Apple notları olarak bir init yönteminden ayarladığınızda çağrılmaz: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.
Klaas

4
Ama bunu yaparken bir dizi özelliği üzerinde çağrılan gibi görünüyor: myArrayProperty.removeAtIndex(myIndex)... Beklenmiyor.
Andreas

4
Atamayı, başlatıcıdaki bir erteleme {} deyiminde, başlatıcı kapsamından çıkıldığında willSet ve didSet yöntemlerinin çağrılmasına neden olabilir. Mutlaka tavsiye etmiyorum, sadece mümkün olduğunu söylüyorum. Sonuçlardan biri, yalnızca özelliği isteğe bağlı olarak bildirirseniz işe yaramasıdır, çünkü kesinlikle başlatandan başlatılmaz.
Marmoy

Lütfen aşağıdaki satırı açıklayınız. Almıyorum, bu yöntem veya değişken var özelliği varChangedListener: (Int, Int) -> Void = {println ("MyProperty değeri ($ 0) 'dan ($ 1)' e değişti)}
Vikash Rajput

Swift 3'te aynı satırdaki özellikleri başlatma DEĞİLDİR. Hızlı 3'e uymak için cevabı değiştirmeniz gerekir.
Ramazan Polat

149

Anladığım kadarıyla, set ve get, hesaplanan özellikler içindir ( depolanan özelliklerden destek yok )

Objective-C'den geliyorsan, adlandırma kurallarının değiştiğini aklından çıkar. Swift'te bir iVar veya örnek değişkeni saklanan özellik olarak adlandırılır

Örnek 1 (salt okunur özellik) - uyarı ile:

var test : Int {
    get {
        return test
    }
}

Bu bir uyarı ile sonuçlanır, çünkü bu özyinelemeli bir işlev çağrısıyla sonuçlanır (alıcı kendisini çağırır).

Örnek 2. Koşullu okuma / yazma - uyarı ile

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

Benzer bir sorun - tekrar tekrar ayarlayıcı çağırdığı için bunu yapamazsınız . Ayrıca, başlatılacak depolanmış özellik olmadığından bu kodun başlatıcılardan şikayet etmeyeceğini unutmayın .

Örnek 3. hesaplanmış özelliği okuma / yazma - destek deposu ile

Burada, gerçek bir saklanan özelliğin koşullu ayarına izin veren bir model bulunmaktadır

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

Not Gerçek veriler _test olarak adlandırılır (herhangi bir veri veya veri birleşimi olsa da) Ayrıca _test aslında bir örnek değişkeni olduğu için bir başlangıç ​​değeri sağlama ihtiyacına da (alternatif olarak bir init yöntemi kullanmanız gerekir) dikkat edin.

Örnek 4. Vasiyetnameyi kullanma

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

Burada willSet ve didSet'in gerçek bir saklanan özellikteki değişikliği durdurduğunu görüyoruz. Bu, bildirim gönderme, senkronizasyon vb. İçin kullanışlıdır ... (aşağıdaki örneğe bakın)

Örnek 5. Somut Örnek - ViewController Kabı

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

BOTH hesaplanmış ve depolanmış özelliklerin kullanımına dikkat edin. Ben aynı değeri iki kez (kötü şeyler olmasını önlemek için!) Ayarlanmasını önlemek için hesaplanan bir özellik kullandım; ViewControllers için bildirimleri iletmek için willSet ve didSet kullandım (bkz. UIViewController belgeleri ve viewController kapsayıcıları hakkında bilgi)

Umarım bu yardımcı olur ve burada herhangi bir hata yaptıysam birisi bağırır!


3
Neden getSet ile birlikte didSet kullanamıyorum .. kullanamıyorum?
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 { yerine kullandıktan sonra uyarı dissapear if (_childVC) {
evfemist

5
get ve set, hesaplanan bir özellik oluşturmak için kullanılır. Bunlar tamamen yöntemdir ve destek depolaması yoktur (örnek değişkeni). willSet ve didSet, depolanan değişken özelliklerindeki değişiklikleri gözlemlemek içindir. Kaputun altında, bunlar depolama ile destekleniyor, ancak Swift'te hepsi bire eridi.
user3675131

Örnek 5'inizde, getbence eklemeniz if _childVC == nil { _childVC = something }ve sonra eklemeniz gerekiyor return _childVC.
JW.ZG

18

Bunlara Mülkiyet Gözlemcileri denir :

Mülk gözlemcileri bir mülkün değerindeki değişiklikleri gözlemler ve bunlara tepki verir. Yeni değer, mülkün geçerli değeri ile aynı olsa bile, bir mülkün değeri her ayarlandığında mülk gözlemcileri çağrılır.

Alıntı: Apple Inc. “Hızlı Programlama Dili”. iBooks. https://itun.es/ca/jEUH0.l

Kullanıcı arayüzü öğeleriyle veri bağlama veya bir mülk değiştirmenin yan etkileri, bir senkronizasyon işlemini tetikleme, arka plan işleme vb.Gibi geleneksel olarak KVO ile yapacağımız şeylere izin verdiğinden şüpheleniyorum .


16

NOT

willSetve didSetyetkilendirme gerçekleşmeden önce başlatıcıda bir özellik ayarlandığında gözlemciler çağrılmaz


16

didSetDeğişkeni farklı bir değere ayarlamak için de kullanabilirsiniz . Bu, Gözlemcinin Özellikler kılavuzunda belirtildiği gibi yeniden çağrılmasına neden olmaz . Örneğin, değeri aşağıdaki gibi sınırlamak istediğinizde yararlıdır:

let minValue = 1

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

value = -10 // value is minValue now.

10

Çok iyi yazılmış mevcut cevaplar soruyu iyi kapsıyor, ancak bazı detaylarda ele almaya değer olduğuna inandığım bir ekten bahsedeceğim.


willSetVe didSetmülkiyet gözlemciler sadece hiç kullanıcı etkileşimi tarafından güncellenir sınıf özellikleri için delege, örneğin çağırmak için kullanılabilir, ama nereye nesne başlatma sırasında temsilci çağırarak kaçınmak istiyoruz.

Klaas'ı kabul edilen cevaba verilen oy hakkında yorumda bulunacağım:

Bir özellik ilk başlatıldığında willSet ve didSet gözlemcileri çağrılmaz. Yalnızca mülkün değeri bir başlatma bağlamının dışında ayarlandığında çağrılırlar.

Bu, örneğin didSetmülkün kendi özel sınıflarınız için temsilci geri çağrıları ve işlevleri için iyi bir başlangıç ​​noktası olduğu anlamına geldiği için oldukça temizdir .

Örnek olarak, bazı anahtar özelliklerle value(örneğin, derecelendirme kontrolündeki konum), aşağıdakilerin alt sınıfı olarak uygulanan bazı özel kullanıcı kontrol nesnelerini göz önünde bulundurun 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.
}

Bundan sonra senin temsilci fonksiyonları için modelinde önemli değişiklikleri gözlemlemek için bazı görünüm denetleyicisi, diyelim ki, kullanılabilir CustomViewControllerEğer doğal delege fonksiyonlarını kullanmayı tercih çok gibi, UITextFieldDelegateiçin UITextFieldnesneler (örneğin textFieldDidEndEditing(...)).

Bu basit örnek için, bir görünüm denetleyicisine çıkışlarından birinin ilişkili model güncellemesine sahip olduğunu bildirmek didSetiçin class özelliğinden bir temsilci geri çağrısı kullanın 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 ...
    //}
}

Burada, valueözellik kapsüllenmiştir, ancak genellikle: bu gibi durumlarda , görünüm denetleyicisindeki ilişkili temsilci işlevi (burada:) kapsamında nesnenin valueözelliğini güncellememeye dikkat edin , aksi takdirde sonsuz özyineleme.customUserControldidChangeValue()


4

Özelliğe yeni bir değer atandığında, özellikler için willSet ve didSet gözlemcileri. Yeni değer geçerli değerle aynı olsa bile bu doğrudur.

willSetDiğer taraftan, geçici bir çözüm bulmak için bir parametre adı gerektiğini unutmayın didSet.

Özellik değeri güncellendikten sonra didSet gözlemcisi çağrılır. Eski değerle karşılaştırır. Toplam adım sayısı arttıysa, kaç yeni adımın atıldığını belirten bir mesaj yazdırılır. DidSet gözlemcisi, eski değer için özel bir parametre adı sağlamaz ve bunun yerine, oldValue varsayılan adı kullanılır.


2

Alıcı ve ayarlayıcı bazen sadece uygun değer değişikliklerini gözlemlemek için uygulanamayacak kadar ağırdır. Genellikle bunun için fazladan değişken değişken işleme ve ekstra kontroller gerekir ve yüzlerce alıcı ve ayarlayıcı yazarsanız bu küçük işçilikten bile kaçınmak istersiniz. Bu şeyler durum içindir.


1
Eşdeğer ayarlayıcı kodu kullanmanın ve buna karşılık gelen performans kodunun performans avantajı olduğunu mu söylüyorsunuz ? Bu cesur bir iddia gibi görünüyor. willSetdidSet
zneak

1
@zneak Yanlış kelime kullandım. İşlemci maliyeti değil programcı çabası talep ediyorum.
Eonil

1

Kendi (temel) sınıfınızda willSetve didSetoldukça tekrarlayıcıdır , bunun yerine a'ya erişen _propertyVariableve istenen ön ve son takibi yapan hesaplanmış bir özelliği (yani get- ve set- yöntemleri) tanımlayabilirsiniz .

Eğer, bununla birlikte , mülk olan bir sınıf geçersiz zaten tanımlanmış , daha sonrawillSet ve didSetvardır kullanışlı ve gereksiz değil!


1

didSetGerçekten kullanışlı olan bir şey , ek yapılandırma eklemek için çıkışları kullandığınızda.

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

ya da willSet'i kullanmak bu çıkış yöntemleri üzerinde bazı etkiler yaratıyor, değil mi?
elia

-5

C # bilmiyorum, ama küçük bir tahminle sanırım neyi anlıyorum

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

yapar. Swift'tekilere çok benziyor, ama aynı değil: Swift'te getFoove sahip değilsiniz setFoo. Bu küçük bir fark değil: değeriniz için temelde bir depolama alanınız olmadığı anlamına gelir.

Swift, özellikleri sakladı ve hesapladı.

Hesaplanan bir özellik (yazılabilirse) vardır getve sahip olabilir set. Ancak, alıcı ve ayarlayıcıdaki kod, eğer gerçekten bazı verileri saklamaları gerekiyorsa, bunu diğer özelliklerde yapmalıdır . Destek deposu yok.

Depolanan bir özellik ise destek depolama alanına sahiptir. Ama yok değil var getve set. Bunun yerine sahiptir willSetve didSethangi değişken değişiklikleri ve nihayetinde tetik yan etkileri gözlemlemek için kullanabilir ve / veya depolanan değeri değiştirebilir. Sen yok willSetve didSetbilgisayarlı özellikleri için, ve bilgisayarlı özellikleri için içeri kod kullanabilirsiniz çünkü onlara ihtiyacım yok setkontrol değişikliklerine.


Bu Swift örneğidir. getFoove setFooalıcıların ve ayarlayıcıların yapmasını istediğiniz her şey için basit yer tutuculardır. C # da onlara ihtiyaç duymaz. (Derleyiciye erişmeden önce sorduğum gibi birkaç sözdizimsel
inceliği özledim

1
Ah tamam. Ancak önemli olan, hesaplanan bir mülkün altında yatan bir depolama alanı olmamasıdır. Ayrıca bkz. Diğer cevabım: stackoverflow.com/a/24052566/574590
Analog Dosya
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.