Sınıf, üst sınıfın gerekli üyelerini uygulamaz


155

Bu yüzden bugün Xcode 6 beta 5'e güncelledim ve Apple'ın sınıflarının neredeyse tüm alt sınıflarında hata aldığımı fark ettim.

Hata durumu:

'X' sınıfı üst sınıfın gerekli üyelerini uygulamıyor

İşte seçtiğim bir örnek, çünkü bu sınıf şu anda oldukça hafif olduğundan yayınlamak kolay olacak.

class InfoBar: SKSpriteNode  { //Error message here

    let team: Team
    let healthBar: SKSpriteNode

    init(team: Team, size: CGSize) {
        self.team = team
        if self.team == Team.TeamGood {
            healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
        }
        else {
            healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
        }
        super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)

        self.addChild(healthBar)

    }

}

Sorum şu: Neden bu hatayı alıyorum ve nasıl düzeltebilirim? Ne uygulamıyorum? Özel bir başlatıcı diyorum.

Yanıtlar:


127

Geliştirici Forumlarındaki bir Apple çalışanından:

"Derleyiciye ve NSCoding uyumlu olmasını istemediğiniz yerleşik programa bildirmenin bir yolu şöyle bir şey yapmaktır:"

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

NSCoding uyumlu olmak istemediğinizi biliyorsanız, bu bir seçenektir. Bu yaklaşımı bir SpriteKit kodumla aldım, çünkü bir storyboard'dan yüklemeyeceğimi biliyorum.


Oldukça iyi çalışan bir başka seçenek, yöntemi bir kolaylık başlangıcı olarak uygulamaktır, şöyle:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

İçindeki bir başlatıcı çağrısını not edin self. Bu, ölümcül bir hata atmaktan kaçınırken, tüm isteğe bağlı olmayan özelliklerin aksine, parametreler için kukla değerleri kullanmanıza izin verir.


Üçüncü seçenek elbette süper çağırırken yöntemi uygulamak ve isteğe bağlı olmayan tüm özelliklerinizi başlatmaktır. Nesne bir film şeridinden yüklenen bir görünümse bu yaklaşımı kullanmalısınız:

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

3
Bununla birlikte, ikinci seçenek çoğu gerçek yaşam durumunda işe yaramaz. Örneğin, gerekli başlatıcıyı ele alalım init(collection:MPMediaItemCollection). Gerçek bir medya öğesi koleksiyonu sağlamalısınız; bu sınıfın amacı budur. Bu sınıf, biri olmadan somutlaştırılamaz. Koleksiyonu analiz edecek ve bir düzine örnek değişkenini başlatacak. Bunun tek ve tek atanan başlatıcısı olmasının anlamı budur! Böylece, init(coder:)burada tedarik edecek hiçbir anlamlı (hatta anlamsız) MPMediaItemCollection yoktur; sadece fatalErroryaklaşım doğrudur.
matt

@matt Doğru, bir veya diğer seçenek farklı durumlarda daha iyi çalışır.
Ben Kane

Doğru, bağımsız olarak ikinci seçeneği keşfettim ve düşündüm ve bazen mantıklı olacak. Örneğin benim di ilan edebilirdim init(collection:MPMediaItemCollection!). Bu init(coder:)nil geçmesine izin verecekti . Ama sonra fark ettim: Hayır, şimdi sadece derleyiciyi kandırıyorsun. Nil geçmek kabul edilemez, bu yüzden at fatalErrorve devam et. :)
matt

1
Bu soruyu biliyorum ve cevapları şimdi biraz eski, ama mevcut cevapların hiçbiri tarafından ele alınmayan bu hatayı gerçekten anlamak için çok önemli olduğunu düşündüğüm bazı noktaları ele alan yeni bir cevap gönderdim.
nhgrif

İyi cevap. Swift'in her zaman süper başlatıcıları devralmadığını anlamanın bu modeli anlamak için şart olduğunu kabul ediyorum.
Ben Kane

71

Bunu tamamen temizlemeye yardımcı olduğunu düşündüğüm mevcut cevaplardan eksik olan Swift'e özgü iki önemli bilgi parçası var.

  1. Bir protokol gerekli bir yöntem olarak bir başlatıcı belirtirse, bu başlatıcı Swift'in requiredanahtar sözcüğü kullanılarak işaretlenmelidir .
  2. Swift, inityöntemlerle ilgili özel bir miras kurallarına sahiptir .

Tl; dr şudur:

Herhangi bir başlatıcı uygularsanız, artık üst sınıfın belirlenmiş başlatıcılarından hiçbirini devralmazsınız.

Varsa, devralınacağınız tek başlatıcılar, geçersiz kıldığınız belirlenmiş bir başlatıcıya işaret eden süper sınıf kolaylık başlatıcılarıdır.

Yani ... uzun versiyona hazýr mýsýn?


Swift, inityöntemlerle ilgili özel bir miras kurallarına sahiptir .

Bunun iki noktadan ikincisi olduğunu biliyorum, ancak ilk noktayı veya requiredbu noktayı anlayana kadar anahtar kelimenin neden var olduğunu anlayamıyoruz. Bu noktayı anladıktan sonra, diğeri oldukça açık hale gelir.

Bu cevabın bu bölümünde ele aldığım tüm bilgiler Apple'ın burada bulunan dokümanlarından alınmıştır .

Apple dokümanlarından:

Objective-C alt sınıflarının aksine, Swift alt sınıfları varsayılan olarak üst sınıf başlatıcılarını devralmaz. Swift'in yaklaşımı, bir üst sınıftan basit bir başlatıcısının daha özel bir alt sınıf tarafından miras alındığı ve alt sınıfın tam veya doğru şekilde başlatılmamış yeni bir örneğini oluşturmak için kullanıldığı bir durumu önler.

Vurgu madeni.

Bu yüzden, doğrudan Apple belgelerinden, Swift alt sınıflarının her zaman üst sınıf inityöntemlerini devralmayacağını (ve genellikle de etmeyeceğini) görüyoruz .

Peki, üst sınıflarından ne zaman miras alıyorlar?

Bir alt sınıfın inityöntemleri üst öğesinden ne zaman devraldığını tanımlayan iki kural vardır . Apple dokümanlarından:

Kural 1

Alt sınıfınız atanmış herhangi bir başlatıcı tanımlamazsa, üst sınıf olarak belirlenmiş tüm başlatıcılarını otomatik olarak devralır.

Kural 2

Alt sınıfınız, kural 1 uyarınca uygun olarak devralınarak veya tanımının bir parçası olarak özel bir uygulama sağlayarak - tüm üst sınıf atanmış başlatıcılarının bir uygulamasını sağlarsa, tüm üst sınıf uygunluk başlatıcılarını otomatik olarak devralır.

Çünkü Kural 2 Bu konuşmadan özellikle alakalı değildir SKSpriteNode'ın init(coder: NSCoder)bir kolaylık yöntem olması pek mümkün değildir.

Böylece, InfoBarsınıfınız requiredeklediğiniz noktaya kadar başlatıcıyı devralıyordu init(team: Team, size: CGSize).

Bu sağlamadıysanız olsaydı inityöntemi ve bunun yerine yapılan InfoBar'isteğe bağlı eklenen s özelliklerini veya varsayılan değerlerle bunları sağlanan, o zaman yine devralan olurdum SKSpriteNode' s init(coder: NSCoder). Ancak, kendi özel başlatıcıyı eklediğimizde, üst sınıfımızın belirlenmiş başlatıcılarını (ve uyguladığımız başlatıcılara işaret etmeyen kolaylık başlatıcıları) devralmayı bıraktık .

Yani, basit bir örnek olarak, bunu sunuyorum:

class Foo {
    var foo: String
    init(foo: String) {
        self.foo = foo
    }
}

class Bar: Foo {
    var bar: String
    init(foo: String, bar: String) {
        self.bar = bar
        super.init(foo: foo)
    }
}


let x = Bar(foo: "Foo")

Bu, aşağıdaki hatayı sunar:

Çağrıdaki 'bar' parametresi için eksik argüman.

resim açıklamasını buraya girin

Eğer bu Objective-C olsaydı, miras almakta sorun olmazdı. Eğer Objective-C Barile a başlatacak olursak initWithFoo:, self.barözellik sadece olurdu nil. Muhtemelen büyük değil, ama mükemmel değil geçerli bir nesne olması için devlet. O var değil . Swift nesne olmak için mükemmel geçerli durum self.baristeğe bağlı değil ve olamaz nil.

Yine, başlatıcıları devralmanın tek yolu kendimizi vermemektir. Yani Bar's silerek miras almaya çalışırsak init(foo: String, bar: String), şöyle:

class Bar: Foo {
    var bar: String
}

Şimdi devralmaya (bir çeşit) geri döndük, ancak bu derlenmeyecek ... ve hata mesajı, neden üst sınıf inityöntemlerini devralmadığımızı tam olarak açıklıyor :

Sorun: 'Bar' sınıfının başlatıcısı yok

Fix-It: Başlatıcılar olmadan depolanmış özellik 'bar' sentezlenmiş başlatıcıları önler

Alt sınıfımıza depolanan özellikler eklediysek, alt sınıfımızın depolanan özellikleri hakkında muhtemelen bilmeyen üst sınıf başlatıcıları ile alt sınıfımızın geçerli bir örneğini oluşturmanın olası bir Swift yolu yoktur.


Tamam, neden uygulamak zorundayım init(coder: NSCoder)? Neden öyle required?

Swift'in inityöntemleri özel bir miras kuralları setiyle oynayabilir, ancak protokol uyumu hala zincirden miras alınır. Bir üst sınıf bir protokole uyuyorsa, alt sınıflarının bu protokole uyması gerekir.

Normalde, bu bir sorun değildir, çünkü çoğu protokol yalnızca Swift'teki özel miras kurallarına uymayan yöntemler gerektirir, bu nedenle bir protokole uyan bir sınıftan miras alıyorsanız, tüm protokolleri miras alırsınız. sınıfın protokol uyumluluğunu karşılamasını sağlayan yöntemler veya özellikler.

Ancak, Swift'in inityöntemlerinin özel bir dizi kural tarafından oynandığını ve her zaman miras alınmadığını unutmayın. Bu nedenle, özel inityöntemler (örneğin NSCoding) gerektiren bir protokole uyan bir sınıf , sınıfın bu inityöntemleri olarak işaretlemesini gerektirir required.

Bu örneği düşünün:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

Bu derlenmiyor. Aşağıdaki uyarıyı oluşturur:

Sorun: Başlatıcı gereksinimi 'init (foo :)' yalnızca son sınıf olmayan 'ConformingClass' sınıfındaki 'zorunlu' başlatıcı tarafından karşılanabilir

Düzeltme: Ekleme gerekli

init(foo: Int)Başlatıcıyı gerekli hale getirmemi istiyor . Sınıfı yaparak da mutlu finaledebilirim (yani sınıftan miras alınamaz).

Peki, alt sınıflarıma ne olur? Bu noktadan sonra, eğer alt sınıf varsa, ben iyiyim. Yine de herhangi bir başlatıcı eklersem, aniden miras kalmıyorum init(foo:). Bu sorunlu çünkü artık artık InitProtocol. Bir protokole uyan bir sınıftan alt sınıf yapamam ve aniden o protokole uymak istemediğime karar veremiyorum. Protokol uygunluğunu miras aldım, ancak Swift'in inityöntem mirası ile çalışma şekli nedeniyle, bu protokole uymak için gerekenlerin bir kısmını miras almadım ve onu uygulamalıyım.


Tamam, hepsi mantıklı. Ama neden daha yararlı bir hata mesajı alamıyorum?

Muhtemelen, sınıfınızın devralınan NSCodingprotokole artık uymadığını ve düzeltmek için uygulamanız gerektiğini belirtmesi durumunda hata mesajı daha net veya daha iyi olabilir init(coder: NSCoder). Elbette.

Ancak Xcode bu mesajı oluşturamaz çünkü bu, gerekli bir yöntemi uygulama veya devralma ile ilgili her zaman asıl sorun olmayacaktır. Protokol uygunluğunun yanı sıra inityöntem requiredyapmanın en az bir nedeni daha vardır ve bu da fabrika yöntemleridir.

Uygun bir fabrika yöntemi yazmak istersem, dönüş türünü belirtmeliyim Self(Swift'in Objective-C eşdeğeri instanceType). Ama bunu yapmak için aslında bir requiredbaşlatıcı yöntemi kullanmam gerekiyor .

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

Bu hatayı oluşturur:

Metatip değeri olan 'Self' sınıf türünde bir nesne oluşturmak için 'gerekli' başlatıcı kullanılmalıdır

resim açıklamasını buraya girin

Temelde aynı problem. Alt sınıf yaparsak Box, alt sınıflarımız sınıf yöntemini devralır factory. Böylece arayabiliriz SubclassedBox.factory(). Ancak olmadan requiredüzerinde anahtar kelime init(size:)yöntemi Box'nin alt sınıfları miras garanti edilmez self.init(size:)olduğunu factoryçağırıyor.

Bu yüzden böyle requiredbir fabrika yöntemi istiyorsak bu yöntemi yapmalıyız ve bu, sınıfımız böyle bir yöntem uygularsa, bir requiredbaşlangıç ​​yöntemimiz olacak ve burada karşılaştığınız sorunların aynısını yapacağız ile NSCodingprotokol.


Sonuçta, Swift'in başlatıcılarının biraz farklı miras kuralları ile oynadığı temel anlayışa dayanır, bu da başlatıcıları üst sınıfınızdan devralmanızın garanti edilmediği anlamına gelir. Bunun nedeni, üst sınıf başlatıcıların yeni depolanan özellikleriniz hakkında bilgi sahibi olmaması ve nesnenizi geçerli bir duruma başlatamamalarıdır. Ancak, çeşitli nedenlerle, bir üst sınıf bir başlatıcıyı olarak işaretleyebilir required. İşe yaradığında, requiredyöntemi miras aldığımız çok spesifik senaryolardan birini kullanabiliriz veya kendimiz uygulamalıyız.

Buradaki ana nokta, burada gördüğünüz hatayı alırsak, sınıfınızın aslında yöntemi uygulamadığı anlamına gelir.

Swift alt sınıflarının her zaman ebeveynlerinin inityöntemlerini devralmadığı gerçeğini incelemek için son bir örnek olarak (ki bu sorunu tam olarak anlamak için kesinlikle merkezi olduğunu düşünüyorum), bu örneği düşünün:

class Foo {
    init(a: Int, b: Int, c: Int) {
        // do nothing
    }
}

class Bar: Foo {
    init(string: String) {
        super.init(a: 0, b: 1, c: 2)
        // do more nothing
    }
}

let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)

Bu derlenemez.

resim açıklamasını buraya girin

Verdiği hata mesajı biraz yanıltıcı:

Çağrıda ekstra 'b' argümanı

Ama gelin olan Barherhangi miras değil Foo'ın initbu miras için ya iki özel vakaların tatmin olmadığı için yöntemlerle init, üst sınıfından yöntemleri.

Bu Objective-C olsaydı, bunu devralınırdık init, çünkü Objective-C nesnelerin özelliklerini başlatmadan mükemmel bir şekilde mutlu olur (geliştirici olarak, bundan memnun olmamalıydınız). Swift'te, bu basitçe yapmayacaktır. Geçersiz bir durumunuz olamaz ve üst sınıf başlatıcılarını devralmak yalnızca geçersiz nesne durumlarına yol açabilir.


Lütfen bu cümlenin ne anlama geldiğini açıklayabilir veya bir örnek verebilir misiniz? "(ve uyguladığımız başlatıcılara işaret etmeyen kolaylık başlatıcıları)"
Abbey Jackson

Mükemmel cevap! Keşke daha çok mesajlar sadece bunun yerine neden böyle olsun diye olsun .
Alexander Vasenin

56

Bu sorun neden ortaya çıktı? Basit gerçek şu ki, sınıfınızın işlemeye hazır olmadığı başlatıcılarla başa çıkmak her zaman önemlidir (yani Objective-C'de, Mac OS X 10.0'da Cocoa'yı programlamaya başladığım günden beri). Belgeler bu konudaki sorumluluklarınız konusunda her zaman oldukça açıktır. Ama kaçımız onları tamamen ve mektubu yerine getirmekle uğraştı? Muhtemelen hiçbirimiz! Ve derleyici onları zorlamadı; tamamen gelenekseldi.

Örneğin, bu atanmış başlatıcı ile Objective-C görünüm denetleyicisi alt sınıfımda:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

... gerçek bir medya öğesi koleksiyonuna geçmemiz çok önemli: örnek, biri olmadan var olamaz. Ama birisinin beni çıplak kemikle başlatmasını engellemek için "tıpa" yazmadım init. Ben gerektiğini (aslında, düzgün konuşma, ben yazdım gereken bir uygulama yazdım initWithNibName:bundle:, kalıtsal belirlenen başlatıcı); ama rahatsız etmek için çok tembeltim, çünkü kendi sınıfımı asla bu şekilde yanlış başlatmayacağımı "biliyordum". Bu bir boşluk bıraktı. Objective-C, birisi olabilir çıplak kemikleri diyoruz init, benim Ivars başlatılmamış bırakarak ve bir raket olmadan dere arttı.

Swift, harika bir şekilde, çoğu durumda beni kendimden kurtarıyor. Bu uygulamayı Swift'e çevirir çevirmez tüm sorun ortadan kalktı. Swift benim için etkili bir şekilde bir durdurucu oluşturur! Eğer init(collection:MPMediaItemCollection)benim sınıfta bildirilen başlatıcısı yalnızca belirlenmiş, ben çıplak kemikleri arayarak başlatıldı edilemez init(). Bu bir mucize!

5. tohumda olan şey, sadece derleyicinin mucizenin işe yaramadığını fark etmesidir init(coder:), çünkü teoride bu sınıfın bir örneği bir uçtan gelebilir ve derleyici bunu engelleyemez - ve uç yükleri, init(coder:)çağrılacaktır. Böylece derleyici tıpayı açıkça yazmanızı sağlar. Ve oldukça doğru.


Bu kadar ayrıntılı cevap için teşekkürler. Bu konuya gerçekten ışık getiriyor.
Julian Osorio

Derleyicinin nasıl kapatılacağını anlattığım için pasta12'ye bir upvote, ama ilk etapta sızlanan şey hakkında bana ipucu verdiğiniz için de size bir upvote.
Garrett Albright

2
Delik açıyor ya da açılmıyor, bu init'i asla söylemeyecektim, bu yüzden beni dahil etmeye zorlamak tamamen sakıncalı. Şişirilmiş kod, hiçbirimizin ihtiyaç duymadığı bir ek yüktür. Ayrıca, sizi her iki birimde de özelliklerinizi başlatmaya zorlar. Anlamsız!
Dan Greenfield

5
@DanGreenfield Hayır, sizi hiçbir şey başlatmaya zorlamaz, çünkü asla aramayacaksanız stackoverflow.com/a/25128815/341994fatalError belgesinde açıklanan durdurucuyu yerleştirirsiniz . Sadece bir kullanıcı Kod Parçacığı olun ve bundan sonra sadece gereken yere yerleştirebilirsiniz. Yarım saniye sürüyor.
matt

1
@nhgrif Şey, adil olmak gerekirse, soru tam bir geri hikaye sormadı. Bu sadece bu reçelden nasıl çıkılacağı ve devam edileceği ile ilgiliydi. Hikayenin tamamı kitabımda verildi: apeth.com/swiftBook/ch04.html#_class_initializers
matt

33

Ekle

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}

3
Bu işe yarıyor, ama bunun bir hata olduğunu düşünmüyorum. başlatıcılar hızlı bir şekilde devralınmaz (kendi başlatıcısı bildirildiğinde) ve bu gerekli anahtar kelimeyle işaretlenir. Tek sorun şimdi ben hiç kullanmıyorum gibi boşa kod çok olacak sınıflarımın her biri için bu yöntemde özellikleri tüm TÜM başlatmanız gerekir. Ya da yapmak istemediğim başlatmayı atlamak için tüm özelliklerimi örtük olarak açılmamış isteğe bağlı türler olarak bildirmek zorunda kalacağım.
Epic Byte

1
Evet! Bir hata olabileceğini söyledikten hemen sonra bunun mantıklı olduğunu fark ettim. Senin gibi bu init yöntemini asla kullanmayacağından, boşa harcanan kod çok olacağını kabul ediyorum. Henüz zarif bir çözümden emin değilim
Gagan Singh

2
Aynı sorunu yaşadım. "Gerekli init" ile mantıklıdır, ancak hızlı olması umduğum "kolay" dil değildir. Tüm bu "isteğe bağlı" dil, gerekenden daha karmaşık hale getiriyor. DSL ve AOP desteği de yok. Giderek daha fazla hayal kırıklığına uğradım.
user810395

2
Evet, tamamen katılıyorum. Birçok mülküm artık opsiyonel olarak ilan edildi çünkü bunu yapmaya zorlandım, gerçekten sıfır olmalarına izin verilmemeli. Bazıları opsiyoneldir çünkü meşru olarak opsiyonel olmalıdırlar (yani geçerli bir değer OLMADI). Ve sonra alt sınıflamadığım sınıflarda opsiyonelleri kullanmam gerekmiyor, bu yüzden işler çok karmaşıklaşıyor ve doğru bir kodlama stili bulamıyorum. Umarım Apple bir şey bulur.
Epic Byte

5
Sanırım, herhangi bir başlatıcıyı bildirmeden gerekli başlatıcıyı tatmin edebileceğiniz anlamına gelir, bu da tüm başlatıcıların miras alınmasına neden olur.
Epic Byte
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.