Инициализатор подкласса и final
Представьте карточку животного в ветеринарной клинике. Кличку в неё вписывает регистратура, и такая графа есть у любого животного. У попугая есть ещё своя графа: сколько слов он знает. Её заполняете вы, причём до того, как отдать карточку в регистратуру. Оттуда карточку сразу несут врачу, а он может заглянуть в любую графу.
Dog из урока о наследовании обходился без своего init: init(name:) он получил от Animal. С попугаем так не получится. У него есть своё свойство words без значения по умолчанию, а init(name:) родителя о нём ничего не знает.
Свой init и super.init
Если у подкласса есть своё свойство без значения по умолчанию, ему нужен свой init. В нём сначала задают свои свойства, а потом вызывают super.init, чтобы родитель задал свои:
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Parrot: Animal {
var words: Int
init(name: String, words: Int) {
self.words = words
super.init(name: name)
}
}
let kesha = Parrot(name: "Kesha", words: 20)
print(kesha.name, kesha.words) // Kesha 20Давайте разберём программу:
var words: Int— своё свойствоParrot, и значения по умолчанию у него нет.init(name: String, words: Int)получает два значения: кличку для родителя и число слов для себя.self.words = wordsзадаёт свойствоParrot— графу попугая в карточке.super.init(name: name)вызываетinitродителя, и тот задаётname— кличку, которую вписывает регистратура.printпечатаетKesha 20: у попугая есть и унаследованное свойствоname, и своёwords.
Получив свой init, Parrot больше не наследует init(name:). Вызов Parrot(name: "Kesha") не скомпилируется, потому что в нём не хватает words:
error: missing argument for parameter 'words' in callПочему сначала свои свойства
Swift требует, чтобы к вызову super.init у всех свойств подкласса уже были значения. Причина в том, что может сделать родитель.
Задав свои свойства, init родителя может вызвать метод экземпляра, например speak(). Если Parrot переопределил этот метод, запустится версия Parrot: версию выбирает класс самого экземпляра. А если она читает words, значение там уже должно быть. Это и есть врач, который заглядывает в любую графу карточки. Порядок обязателен всегда, даже если родитель никаких методов не вызывает.
Здесь картинка с клиникой неточна. Регистратура сама спрашивает кличку, а в Swift её передаёт подкласс: super.init(name: name) отдаёт родителю параметр name, который получил init попугая.
Запрет наследования: final
Слово final запрещает наследовать и переопределять:
- от класса
final classнельзя унаследовать; - метод
final funcнельзя переопределить в подклассе.
Так автор класса защищает его от изменений, которых он не задумывал. Например, если объявить final class Parrot: Animal, то строка class Macaw: Parrot {} не скомпилируется:
error: inheritance from a final class 'Parrot'На собеседовании про final спрашивают и о скорости. Без него версию метода выбирают во время работы программы, по классу экземпляра. Подкласс не напишет свою версию final-метода, поэтому компилятор может вызвать его напрямую, без выбора.
Частая ошибка
В init подкласса сначала вызвали super.init, а потом задали своё свойство:
class Cat: Animal {
var lives: Int
init(name: String, lives: Int) {
super.init(name: name) // ✗
self.lives = lives
}
}error: property 'self.lives' not initialized at super.init callПрограмма не компилируется: к вызову super.init у свойства lives ещё нет значения. Поменяйте строки местами: сначала self.lives = lives, потом super.init(name: name).
Главное
initподкласса сначала задаёт свои свойства, затем вызываетsuper.init; в обратном порядке программа не скомпилируется.- Порядок нужен, потому что
initродителя может вызвать переопределённый метод, а тот — прочитать свойство подкласса. - Подкласс со своим
initне наследуетinitродителя:Parrot(name: "Kesha")не скомпилируется. - От
final classнельзя унаследовать, аfinal funcнельзя переопределить.