Рассмотрим два класса:
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
's init
, x
снова указывая значение по умолчанию , когда удобство init
в A
этом вполне подойдет?
class
initialization
swift
Роберт
источник
источник
Ответы:
Это Правило 1 правил «цепочки инициализаторов», как указано в Руководстве по программированию Swift, которое гласит:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html
Акцент мой. Назначенные инициализаторы не могут вызывать удобные инициализаторы.
Существует диаграмма, которая сопровождается правилами, демонстрирующими, какие "направления" инициализатора разрешены:
источник
SCNGeometry
: вы можете добавитьSCNGeometryElement
s только с помощью инициализатора удобства, поэтому от него нельзя наследовать.Рассмотреть возможность
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:, b: ).
источник
Это потому, что вы можете получить бесконечную рекурсию. Рассмотреть возможность:
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()
.Назначенные / удобные правила инициализации разработаны так, что инициализация класса всегда будет правильной.
источник
Я нашел обходной путь для этого. Это не очень красиво, но решает проблему незнания значений суперкласса или желания установить значения по умолчанию.
Все, что вам нужно сделать, это создать экземпляр суперкласса, используя удобство
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's convenience init, gets instance of A with default x value let intermediate = A() super.init(x: intermediate.x) } }
источник
Рассмотрите возможность извлечения кода инициализации из удобной
init()
для вас новой вспомогательной функцииfoo()
, вызовите,foo(...)
чтобы выполнить инициализацию в вашем подклассе.источник
Посмотрите видео WWDC «403 промежуточный Swift» в 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) } }
Но теперь рассмотрим подкласс Вирма: Вирм - это Дракон без ног и крыльев. Значит, Инициализатор для виверны (2 ноги, 2 крыла) не подходит! Этой ошибки можно избежать, если просто невозможно вызвать удобный Wyvern-Initializer, а только полностью назначенный инициализатор:
class Wyrm: Dragon { init() { super.init(legs: 0, isFlying: false) } }
источник
initWyvern
имеет смысл вызывать?Wyrm
переопределить количество этапов после вызова вспомогательного инициализатора.Почему бы вам просто не использовать два инициализатора - один со значением по умолчанию?
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
источник