Почему не могут инициализаторы Swift вызывать удобные инициализаторы на их суперклассе?

Рассмотрим два класса:

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

Я не понимаю, почему это запрещено. В конечном итоге каждый назначенный классом инициализатор вызывается с любыми значениями, которые им нужны, поэтому зачем мне повторять себя в B init, снова задав значение по умолчанию для x, когда удобство init в A будет прекрасно?

Ответ 1

Это правило 1 правил "Цепочки инициализаторов", как указано в Руководстве по быстрому программированию, которое гласит:

Правило 1: инициаторы Назначенные должны вызывать инициализатор назначенный из своих немедленный суперкласс.

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

Акцент мой. Назначенные инициализаторы не могут вызывать инициализаторы удобства.

Существует диаграмма, которая согласуется с правилами, чтобы продемонстрировать, какие инициализационные "направления" разрешены:

Initializer Chaining

Ответ 2

Рассмотрим

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 переопределены, класс B наследует все удобные инициализаторы A. Таким образом, выполнение этого приведет к выводу

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

Теперь, если назначенный инициализатор B.init(a: b:) будет разрешен для вызова инициализатора удобства базового класса A.init(a:), это приведет к рекурсивному вызову B.init(a: б:.)

Ответ 3

Это потому, что вы можете обрести бесконечную рекурсию. Рассмотрим:

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

и посмотрите:

let a = SubClass()

который вызовет SubClass.init(), который вызовет SuperClass.init(value:), который вызовет SubClass.init().

Назначенные/удобные правила инициализации предназначены для того, чтобы инициализация класса всегда была правильной.

Ответ 4

Я нашел для этого работу. Это не очень красиво, но решает проблему не знать значения суперкласса или не хочет устанавливать значения по умолчанию.

Все, что вам нужно сделать, это создать экземпляр суперкласса, используя удобство init, прямо в init подкласса. Затем вы вызываете назначенный init супер, используя только что созданный экземпляр.

class A {
    var x: Int

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

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

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

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

Ответ 5

Посмотрите на WWDC-видео "403 промежуточное Свифт" в 18:30 для подробного объяснения инициализаторов и их наследования. Как я понял, рассмотрим следующее:

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

Но теперь рассмотрим подкласс Wyrm: Вирм - это Дракон без ног и крыльев. Поэтому Инициализатор для Wyvern (2 ноги, 2 крыла) ошибочен! Эту ошибку можно избежать, если удобство Wyvern-Initializer просто не может быть вызвано, а только полный назначенный инициализатор:

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

Ответ 6

Почему у вас просто нет двух инициализаторов - один со значением по умолчанию?

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

Ответ 7

Рассмотрите извлечение кода инициализации из удобного init() в новую вспомогательную функцию foo(), вызовите foo(...), чтобы выполнить инициализацию в вашем подклассе.