Bunu tamamen temizlemeye yardımcı olduğunu düşündüğüm mevcut cevaplardan eksik olan Swift'e özgü iki önemli bilgi parçası var.
- Bir protokol gerekli bir yöntem olarak bir başlatıcı belirtirse, bu başlatıcı Swift'in
required
anahtar sözcüğü kullanılarak işaretlenmelidir .
- Swift,
init
yö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, init
yöntemlerle ilgili özel bir miras kurallarına sahiptir .
Bunun iki noktadan ikincisi olduğunu biliyorum, ancak ilk noktayı veya required
bu 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 init
yö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 init
yö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, InfoBar
sınıfınız required
eklediğiniz noktaya kadar başlatıcıyı devralıyordu init(team: Team, size: CGSize)
.
Bu sağlamadıysanız olsaydı init
yö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.
Eğer bu Objective-C olsaydı, miras almakta sorun olmazdı. Eğer Objective-C Bar
ile 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.bar
isteğ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 init
yö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 init
yö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 init
yöntemlerinin özel bir dizi kural tarafından oynandığını ve her zaman miras alınmadığını unutmayın. Bu nedenle, özel init
yöntemler (örneğin NSCoding
) gerektiren bir protokole uyan bir sınıf , sınıfın bu init
yö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 final
edebilirim (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 init
yö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 NSCoding
protokole 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 init
yöntem required
yapmanı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 required
baş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
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 required
bir fabrika yöntemi istiyorsak bu yöntemi yapmalıyız ve bu, sınıfımız böyle bir yöntem uygularsa, bir required
başlangıç yöntemimiz olacak ve burada karşılaştığınız sorunların aynısını yapacağız ile NSCoding
protokol.
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, required
yö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 init
yö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.
Verdiği hata mesajı biraz yanıltıcı:
Çağrıda ekstra 'b' argümanı
Ama gelin olan Bar
herhangi miras değil Foo
'ın init
bu 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.
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; sadecefatalError
yaklaşım doğrudur.