Swift'te başlatma sırasında didSet'in çağrılmasına izin vermek mümkün müdür?


217

Soru

Apple'ın belgeleri şunları belirtir:

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.

Başlatma sırasında bunları çağırmaya zorlamak mümkün mü?

Neden?

Diyelim ki bu dersim var

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

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

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

doStuffİşlem çağrılarını daha özlü hale getirmek için yöntemi oluşturdum , ancak didSetişlevi işlev içindeki özelliği işlemek istiyorum . Başlatma sırasında bunu çağırmaya zorlamanın bir yolu var mı?

Güncelleme

Sadece benim sınıf için uygunluk intializer kaldırmaya karar verdi ve başlattıktan sonra özelliği ayarlamak için zorladı. Bu didSether zaman çağrılacak bilmeme izin veriyor . Bunun genel olarak daha iyi olup olmadığına karar vermedim, ancak durumuma iyi uyuyor.


dürüst olmak gerekirse en iyisi sadece "bu çabuk çalışmanın nasıl olduğunu kabul etmek" tir. var ifadesine satır içi başlatma değeri koyarsanız, elbette "didSet" çağrılmaz. bu nedenle "init ()" durumu "didSet" olarak da adlandırılmamalıdır. Hepsi mantıklı.
Fattie

@Logan Sorunun kendisi sorumu yanıtladı;) teşekkürler!
AamirR

2
Kolaylık başlatıcıyı kullanmak istiyorsanız, aşağıdakilerle birleştirebilirsiniz defer:convenience init(someProperty: AnyObject) { self.init() defer { self.someProperty = someProperty }
Darek Cieśla

Yanıtlar:


100

Kendi bir set yöntemi oluşturun ve bunu init yönteminizde kullanın:

class SomeClass {
    var someProperty: AnyObject! {
        didSet {
            //do some Stuff
        }
    }

    init(someProperty: AnyObject) {
        setSomeProperty(someProperty)
    }

    func setSomeProperty(newValue:AnyObject) {
        self.someProperty = newValue
    }
}

somePropertyType: AnyObject!(örtük olarak paketlenmemiş isteğe bağlı) olarak bildirerek , somePropertyayarlanmadan kendiliğinden tam olarak başlatılmasına izin verirsiniz . Aradığınızda setSomeProperty(someProperty)size bir eşdeğerini diyoruz self.setSomeProperty(someProperty). Normalde bunu yapamazsınız çünkü benlik tam olarak başlatılmamıştır. Yana somePropertybaşlatma gerektirmez ve öz bir yöntem bağımlı aradığınız, Swift başlatma bağlamı bırakır ve didSet çalışacaktır.


3
Bu neden init içindeki self.someProperty = newValue değerinden farklı olabilir? Çalışan bir test durumunuz var mı?
mmmmmm

2
Neden çalıştığını bilmiyorum - ama işe yarıyor. Yeni değeri init () - Method içinde doğrudan ayarlamak didSet () öğesini çağırmaz, ancak verilen yöntemi kullanarak setSomeProperty () kullanır.
Oliver

50
Sanırım burada neler olduğunu biliyorum. somePropertyType: AnyObject!(örtük olarak paketlenmemiş isteğe bağlı) olarak bildirerek , ayarlanmadan selftam olarak başlatılmasına izin verirsiniz someProperty. Aradığınızda setSomeProperty(someProperty)size bir eşdeğerini diyoruz self.setSomeProperty(someProperty). Normalde selftam olarak başlatılmadığı için bunu yapamazsınız . Yana somePropertybaşlatma gerektirmez ve bir yöntem bağımlı çağırıyorlar self, Swift başlatma bağlamı bırakır ve didSetçalışacaktır.
Logan

3
Gerçek init () dışında ayarlanan herhangi bir şeye benziyor didSet çağırır. Kelimenin tam anlamıyla ayarlanmayan herhangi bir şey init() { *HERE* }didSet çağrılmış olacaktır. Bu soru için harika, ancak bazı değerleri ayarlamak için ikincil bir işlev kullanmak didSet'in çağrılmasına yol açar. Bu ayarlayıcı işlevini yeniden kullanmak istiyorsanız harika değil.
WCByrne

4
@Ciddi bir şekilde, Swift'e sağduyu veya mantık uygulamaya çalışmak saçmadır. Ancak upvotes güvenebilir veya kendiniz deneyebilirsiniz. Sadece yaptım ve işe yarıyor. Ve evet, hiç mantıklı değil.
Dan Rosenstark

305

Eğer kullanırsanız deferbir iç başlatıcısı , zaten başlatılmış ettik isteğe bağlı herhangi özelliklerini veya diğer bir güncelleştirmenin isteğe bağlı olmayan özelliklere güncellenmesi için ve sonra herhangi aradım super.init()sonra, yöntemler willSet, didSetvb çağrılır. Bunu doğru yerlerde aramayı takip etmeniz gereken ayrı yöntemler uygulamaktan daha uygun buluyorum.

Örneğin:

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

Verim olacak:

I'm going to change to 3.14
Now I'm 3.14

6
Arsız küçük numara, hoşuma gitti ... Swift'in garip tuhaflığı.
Chris Hatton

4
Harika. Kullanmanın başka bir yararlı örneğini buldunuz defer. Teşekkürler.
Ryan

1
Erteleme büyük kullanın! Keşke size birden fazla oy verebilsem.
Christian Schnorr

3
çok ilginç .. Daha önce hiç böyle ertelendiğini görmedim. Etkili ve işe yarıyor.
aBikis

Ancak myOptionalField öğesini performans açısından çok iyi olmayan iki kez ayarladınız. Ya ağır hesaplamalar yapılırsa?
Yakiv Kovalskyi

77

Oliver'ın cevabının bir çeşidi olarak, çizgileri bir kapanışta sarabilirsiniz. Örneğin:

class Classy {

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

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

}

Edit: Brian Westphal'ın cevabı daha güzel imho. Onun hakkında güzel olan şey, niyetini ima etmesi.


2
Bu zekidir ve sınıfı gereksiz yöntemlerle kirletmez.
pointum

Harika cevap, teşekkürler! Swift 2.2'de kapanışı her zaman parantez içinde sarmanız gerektiğini belirtmek gerekir.
Max

@Max Cheers, örnek şimdi parens içerir
original_username

2
Akıllı cevap! Bir not: sınıfınız başka bir şeyin alt sınıfı ise, önce ({self.foo = foo}) ()
kcstricks

1
@Hlung, gelecekte herhangi bir şeyden daha kırılma olasılığı daha yüksek, ama hala kod yorum yapmadan ne yaptığını çok net olmayan bir sorun var. En azından Brian'ın "erteleme" yanıtı, okuyucuyu başlatma işleminden sonra kodu gerçekleştirmek istediğimize dair bir ipucu veriyor.
original_username

10

Aynı problemim vardı ve bu benim için işe yarıyor

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

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

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

nereden aldın?
Vanya

3
Bu yaklaşım artık işe yaramıyor. Derleyici iki hata atar: - Depolanan tüm özellikler başlatılmadan önce '$ defer' yöntem çağrısında kullanılan 'self' - Depolanan tüm özellikleri başlatmadan başlatıcıdan geri dön
Edward B

Haklısın ama bir çözüm var. Sadece bazıÖzellikleri örtülü paketlenmemiş veya isteğe bağlı olarak bildirmeniz ve başarısızlıkları önlemek için varsayılan değer olarak nil birleştirmesi sağlamanız gerekir. TLDR; someProperty isteğe bağlı türü olmalıdır
Mark

2

Bunu bir alt sınıfta yaparsanız çalışır

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()

In aÖrneğin, didSettetiklenmez. Ancak, börneğin, didSetalt sınıfta olduğu için tetiklenir. O ne bir şey yapmak zorunda initialization context, gerçek anlamını bu durumda superclassbu konuda yaptığımız bakım


1

Bu bir çözüm olmasa da, bunun için alternatif bir yol bir sınıf yapıcı kullanmak olacaktır:

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

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

1
Bu çözüm, somePropertybaşlatma sırasında ona bir değer vermeyeceği için seçeneğini değiştirmenizi gerektirir .
Mick MacCallum

1

Üst sınıfınızda bulunan bir özellik için çağırmak willSetveya didSetiçeride çağırmak istediğiniz özel durumda init, süper mülkünüzü doğrudan atayabilirsiniz:

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

O Not Charlesism bir kapakla çözümü her zaman bu durumda da çalışacak. Benim çözümüm sadece bir alternatif.


-1

Bunu obj-с şekilde çözebilirsiniz:

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
Merhaba @yshilov, bunun teknik olarak self.somePropertybaşlangıçta kapsamdan ayrıldığını söylediğinde umduğum problemi gerçekten çözmediğini sanmıyorum . Aynı şeyi yaparak da gerçekleştirilebileceğine inanıyorum var someProperty: AnyObject! = nil { didSet { ... } }. Yine de, bazıları bunu bir çalışma olarak takdir edebilir, ayrıca ek için teşekkürler
Logan

@ Hayır, işe yaramaz. didSet init () içinde tamamen yok sayılır.
yshilov
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.