Swift başlatıcıları neden süper sınıflarında kolaylık başlatıcıları çağıramıyor?


88

İki sınıfı düşünün:

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        super.init() // Error: Must call a designated initializer of the superclass 'A'
    }
}

Buna neden izin verilmediğini anlamıyorum. Sonuçta, her sınıfın belirlenen başlatıcı halde neden kendimi tekrarlamak gerekiyor, ihtiyaç duydukları herhangi değerlerle denir Bs' initiçin varsayılan değerini belirleyerek xkolaylık zaman, yine initde Asadece yeterli olacaktır?


2
Bir cevap aradım ama beni tatmin edecek bir cevap bulamıyorum. Muhtemelen bir uygulama nedenidir. Belki başka bir sınıfta belirlenmiş başlatıcıları aramak, kolaylık başlatıcıları veya buna benzer bir şeyi aramaktan çok daha kolaydır.
Sulthan

@Robert, aşağıdaki yorumlarınız için teşekkürler. Sanırım bunları sorunuza ekleyebilir, hatta aldığınız yanıtla bir yanıt bile gönderebilirsiniz: "Bu tasarım gereğidir ve bu alanda ilgili hatalar çözülmüştür." Görünüşe göre sebebini açıklayamıyorlar veya istemiyorlar.
Ferran Maylinch

Yanıtlar:


24

Bu, Swift Programlama Kılavuzunda belirtildiği gibi "Başlatıcı Zincirleme" kurallarının 1. Kuralı olup, aşağıdaki gibidir:

Kural 1: Atanan başlatıcılar , hemen üst sınıflarından belirlenmiş bir başlatıcıyı çağırmalıdır .

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html

Vurgu benim. Atanan başlatıcılar kolaylık başlatıcıları çağıramaz.

Hangi başlatıcı "yönlerine" izin verildiğini gösteren kurallarla birlikte giden bir şema vardır:

Başlatıcı Zinciri


80
Ama neden bu şekilde yapılıyor? Dokümantasyon sadece tasarımı basitleştirdiğini söylüyor, ancak belirlenmiş başlatıcılarda sürekli olarak varsayılan değerleri belirterek kendimi tekrarlamak zorunda kalırsam durumun nasıl olduğunu anlamıyorum. başlatıcılar yapacak mı?
Robert

5
Bu sorudan birkaç gün sonra 17266917 numaralı bir hata gönderdim ve kolaylık başlatıcıları arayabilmeyi istedim. Henüz yanıt yok, ancak diğer Swift hata raporlarım için de yanıt almadım!
Robert

8
Umarım kolaylık başlatıcıları aramaya izin verirler. SDK'daki bazı sınıflar için, belirli bir davranışı elde etmenin başka bir yolu yoktur, ancak kolaylık başlatıcıyı çağırır. Bakınız SCNGeometry: SCNGeometryElementyalnızca kolaylık başlatıcıyı kullanarak s ekleyebilirsiniz , bu nedenle ondan miras alınamaz.
Spak

4
Bu çok zayıf bir dil tasarımı kararı. Yeni uygulamamın en başından hızlı bir şekilde geliştirmeye karar verdim, alt sınıfımdan NSWindowController.init'i (windowNibName) aramam gerekiyor ve bunu yapamıyorum :(
Uniqus

4
@Kaiserludi: Yararlı bir şey yok, şu yanıtla kapatıldı: "Bu, tasarım gereğidir ve bu alanda ilgili tüm hatalar çözüldü."
Robert

21

Düşünmek

class A
{
    var a: Int
    var b: Int

    init (a: Int, b: Int) {
        print("Entering A.init(a,b)")
        self.a = a; self.b = b
    }

    convenience init(a: Int) {
        print("Entering A.init(a)")
        self.init(a: a, b: 0)
    }

    convenience init() {
        print("Entering A.init()")
        self.init(a:0)
    }
}


class B : A
{
    var c: Int

    override init(a: Int, b: Int)
    {
        print("Entering B.init(a,b)")
        self.c = 0; super.init(a: a, b: b)
    }
}

var b = B()

A sınıfının tüm belirlenmiş başlatıcıları geçersiz kılındığından, B sınıfı, A'nın tüm kolaylık başlatıcılarını devralacaktır.

Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)

Şimdi, belirtilen başlatıcı B.init (a: b :) 'nin temel sınıf uygunluk başlatıcısı A.init (a :)' yı çağırmasına izin verilirse, bu B.init (a:, b: ).


Etrafta çalışmak kolay. Belirlenmiş bir başlatıcı içinden bir süper sınıfın uygunluk başlatıcısını çağırdığınız anda, süper sınıfın uygunluk başlatıcıları artık miras alınmayacaktır.
Fluidsonic

@fluidsonic ama bu çılgınlık olurdu çünkü yapınız ve sınıf yöntemleriniz nasıl kullanıldığına bağlı olarak değişecekti. Hata ayıklama eğlencesini hayal edin!
kdazzle

2
@kdazzle Ne sınıfın yapısı ne de sınıf yöntemleri değişecektir. Neden yapmalılar? - Aklıma gelen tek sorun, kolaylık başlatıcılarının devralındığında bir alt sınıfın belirlenmiş başlatıcısına dinamik olarak ve miras alınmadığında ancak bir alt sınıftan atandığında kendi sınıfının belirlenmiş başlatıcısına statik olarak delege etmesi gerektiğidir.
Fluidsonic

Normal yöntemlerle de benzer bir özyineleme problemi yaşayabileceğinizi düşünüyorum. Bu, yöntemleri ararken kararınıza bağlıdır. Örneğin, bir dilin özyinelemeli çağrılara izin vermemesi aptalca olurdu çünkü sonsuz bir döngüye girebilirsiniz. Programcılar ne yaptıklarını anlamalıdır. :)
Ferran Maylinch

13

Çünkü sonsuz bir özyineleme ile sonuçlanabilirsin. Düşünmek:

class SuperClass {
    init() {
    }

    convenience init(value: Int) {
        // calls init() of the current class
        // so init() for SubClass if the instance
        // is a SubClass
        self.init()
    }
}

class SubClass : SuperClass {
    override init() {
        super.init(value: 10)
    }
}

ve şuna bakın:

let a = SubClass()

Hangisi arayacak, SubClass.init()hangisi SuperClass.init(value:)arayacak SubClass.init().

Atanan / uygunluk başlatma kuralları, bir sınıf başlatma her zaman doğru olacak şekilde tasarlanmıştır.


1
Bir alt sınıf, kendi üst sınıfından açıkça uygunluk başlatıcıları çağırmayabilirken , alt sınıfın tüm üst sınıf atanmış başlatıcılarının uygulamasını sağladığı göz önüne alındığında ( örneğin, bu örnek ) bunları devralabilir . Bu nedenle, yukarıdaki örneğiniz gerçekten doğrudur, ancak açık bir şekilde bir üst sınıfın uygunluk başlatıcısı ile ilgili olmayan özel bir durumdur, bunun yerine atanmış bir başlatıcının uygun olanı çağırmayabileceği gerçeğiyle ilgilidir , çünkü bu yukarıdaki gibi yinelemeli senaryolara yol açacaktır. .
dfrib

1

Bunun için bir çalışma buldum. Çok hoş değil, ancak bir süper sınıfın değerlerini bilmeme veya varsayılan değerleri belirlemeyi isteme sorununu çözüyor.

Tek yapmanız gereken tek şey, üst sınıf bir örneğini oluşturmak kolaylık kullanıyorsa initsağ, initalt sınıfına ait. Daha sonra initoluşturduğunuz örneği kullanarak süperin atanmışını çağırırsınız .

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        // calls A's convenience init, gets instance of A with default x value
        let intermediate = A() 

        super.init(x: intermediate.x) 
    }
}

1

Kullanışlılığınızdan init()yeni bir yardımcı fonksiyona başlatma kodunu çıkarmayı düşünün , alt sınıfınızda başlatma yapmak için foo()arayın foo(...).


İyi bir öneri, ama aslında soruyu cevaplamıyor.
SwiftsNamesake

0

Başlatıcıların ve miraslarının ayrıntılı bir açıklaması için 18: 30'daki WWDC videosu "403 orta seviye Swift" e bakın. Anladığım kadarıyla şunları düşünün:

class Dragon {
    var legs: Int
    var isFlying: Bool

    init(legs: Int, isFlying: Bool) {
        self.legs = legs
        self.isFlying = isFlying
    }

    convenience initWyvern() { 
        self.init(legs: 2, isFlying: true)
    }
}

Ama şimdi bir Wyrm alt sınıfı düşünün: Bir Ejder, bacakları ve kanatları olmayan bir Ejderhadır. Yani bir Wyvern için Başlatıcı (2 bacak, 2 kanat) bunun için yanlış! Kolaylık Wyvern-Başlatıcı sadece tam olarak belirlenmiş Başlatıcı çağrılamıyorsa, bu hata önlenebilir:

class Wyrm: Dragon {
    init() {
        super.init(legs: 0, isFlying: false)
    }
}

12
Bu gerçekten bir sebep değil. Ya initWyvernçağrılması mantıklı olduğunda bir alt sınıf yaparsam ?
Sulthan

4
Evet, ikna olmadım. WyrmBir kolaylık başlatıcı çağırdıktan sonra bacak sayısını geçersiz kılan hiçbir şey yok .
Robert

WWDC videosunda verilen neden budur (sadece arabalar -> yarış arabaları ve boole hasTurbo özelliği ile). Alt sınıf tüm belirlenmiş başlatıcıları uygularsa, o da kolaylık başlatıcılarını devralır. Bunun anlamını anlıyorum, ayrıca gerçekten Objective-C'nin çalışma şekliyle ilgili pek sorun yaşamadım. Ayrıca ilk olarak Objective-C'deki gibi değil, init'te super.init'i son olarak çağırmak için yeni kurala bakın.
Ralf

IMO, super.init'i "son" olarak adlandıran sözleşme kavramsal olarak yeni değildir, amaç-c'deki ile aynıdır, sadece orada, her şeye otomatik olarak bir başlangıç ​​değeri (sıfır, 0, her neyse) atanmaktadır. Sadece Swift'de, bu aşamayı başlatmayı kendimiz yapmalıyız. Bu yaklaşımın bir yararı da, farklı bir başlangıç ​​değeri atama seçeneğimizin olmasıdır.
Roshan

-1

Neden biri varsayılan değere sahip iki başlatıcınız yok?

class A {
  var x: Int

  init(x: Int) {
    self.x = x
  }

  init() {
    self.x = 0
  }
}

class B: A {
  override init() {
    super.init()

    // Do something else
  }
}

let s = B()
s.x // 0
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.