Є два абсолютно важливих фрагмента інформації, що стосується Свіфта, відсутні в існуючих відповідях, які, на мою думку, допомагають повністю прояснити це.
- Якщо протокол визначає ініціалізатор як необхідний метод, цей ініціалізатор повинен бути позначений за допомогою
required
ключового слова Swift .
- Свіфт має спеціальний набір правил успадкування щодо
init
методів.
Т.Л., д - р це:
Якщо ви реалізуєте будь-які ініціалізатори, ви більше не успадковуєте жоден із призначених ініціалізаторів суперкласу.
Єдині ініціалізатори, якщо такі є, які ви успадкуєте, - це ініціалізатори зручності суперкласу, які вказують на призначений ініціалізатор, який ви переотримали.
Отже ... готовий до довгої версії?
Свіфт має спеціальний набір правил успадкування щодо init
методів.
Я знаю, що це був другий з двох пунктів, які я зробив, але ми не можемо зрозуміти перший пункт, або чому required
ключове слово навіть існує, поки ми не зрозуміємо цей момент. Як тільки ми зрозуміємо цей момент, інший стає досить очевидним.
Вся інформація, яку я висвітлюю в цьому розділі цієї відповіді, з документації Apple, знайденої тут .
З документів Apple:
На відміну від підкласів у Objective-C, підкласи Swift не успадковують свої ініціалізатори надкласу за замовчуванням. Підхід Swift запобігає ситуації, коли простий ініціалізатор від суперкласу успадковується більш спеціалізованим підкласом і використовується для створення нового екземпляра підкласу, який не повністю або правильно ініціалізований.
Наголос мій.
Отже, прямо з документів Apple, що там, ми бачимо, що підкласи Свіфта не завжди (і зазвичай не є) успадковують init
методи їхнього суперкласу .
Отже, коли вони успадковують від свого суперкласу?
Є два правила, які визначають, коли підклас успадковує init
методи від свого батьківського. З документів Apple:
Правило 1
Якщо ваш підклас не визначає жодних призначених ініціалізаторів, він автоматично успадковує всі його ініціалізатори, призначені для суперкласу.
Правило 2
Якщо ваш підклас забезпечує реалізацію всіх своїх ініціалізаторів, призначених для суперкласу - або успадковуючи їх відповідно до правила 1, або надаючи користувацьку реалізацію як частину свого визначення - він автоматично успадковує всі ініціалізатори зручності суперкласу.
Правило 2 цієї статті не має особливого значення для цієї розмови , тому що SKSpriteNode
«s init(coder: NSCoder)
навряд чи буде зручний метод.
Отже, ваш InfoBar
клас успадкував required
ініціалізатор аж до моменту, який ви додали init(team: Team, size: CGSize)
.
Якби ви не надали цей init
метод , і замість цього зробили свій InfoBar
«и додані властивості по бажанню або при умови їх значення по замовчуванню, то ви б до цих пір був успадкувати SKSpriteNode
» S init(coder: NSCoder)
. Однак, додавши власний ініціалізатор, ми перестали успадковувати призначені ініціалізатори нашого суперкласу (та зручні ініціалізатори, які не вказували на реалізовані нами ініціалізатори).
Отже, як спрощений приклад, я представляю це:
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")
Яка подає таку помилку:
Відсутній аргумент для параметра "bar" у виклику.
Якби це Objective-C, у нас би не було проблем із успадкуванням. Якби ми ініціалізували a Bar
з initWithFoo:
в Objective-C, self.bar
властивість буде просто nil
. Це, мабуть, не чудово, але це абсолютно дійсний стан для об'єкта, який повинен знаходитися. Це не зовсім допустимий стан для об'єкта Swift, який знаходиться. self.bar
Не є необов'язковим і не може бути nil
.
Знову ж таки, єдиний спосіб наслідувати ініціалізатори - це не надання власного. Отже, якщо ми намагаємось успадкувати, видаляючи Bar
's init(foo: String, bar: String)
, як таке:
class Bar: Foo {
var bar: String
}
Тепер ми повернулися до успадкування (свого роду), але це не буде компілюватися ... і повідомлення про помилку пояснює саме те, чому ми не успадковуємо init
методи суперкласи :
Проблема: Клас 'Bar' не має ініціалізаторів
Fix-It: Збережена властивість 'bar' без ініціалізаторів запобігає синтезуванню ініціалізаторів
Якщо ми додали збережені властивості до нашого підкласу, не існує можливого швидкого способу створити дійсний екземпляр нашого підкласу з ініціалізаторами надкласу, який не міг би знати про збережені властивості нашого підкласу.
Гаразд, ну навіщо мені взагалі реалізовувати init(coder: NSCoder)
це? Чому саме так required
?
init
Методи Свіфта можуть грати за допомогою спеціального набору правил успадкування, але відповідність протоколу все ж успадковується по ланцюгу. Якщо батьківський клас відповідає протоколу, його підкласи повинні відповідати цьому протоколу.
Зазвичай це не проблема, тому що для більшості протоколів потрібні лише методи, які не грають за спеціальними правилами успадкування в Swift, тому якщо ви успадковуєте клас, який відповідає протоколу, ви також успадковуєте всі методи або властивості, що дозволяють класу задовольнити відповідність протоколу.
Однак пам’ятайте, init
методи Свіфт грають за спеціальним набором правил і не завжди успадковуються. Через це клас, який відповідає протоколу, який вимагає спеціальних init
методів (таких як NSCoding
), вимагає, щоб клас позначав ці init
методи як required
.
Розглянемо цей приклад:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Це не компілюється. Він генерує таке попередження:
Проблема: Вимогу ініціалізатора 'init (foo :)' може задовольнятися лише необхідним ініціалізатором у нефінальному класі 'ConformingClass'
Виправити: Вставити потрібно
Мені потрібно зробити init(foo: Int)
необхідний ініціалізатор. Я також міг би зробити це щасливим, зробивши клас final
(тобто клас не може бути успадкований від).
Отже, що станеться, якщо я підклас? З цього моменту, якщо я підкласу, я добре. Якщо я додаю якісь ініціалізатори, я раптом більше не успадковую init(foo:)
. Це проблематично, тому що зараз я більше не відповідаю цьому InitProtocol
. Я не можу підклас класу, який відповідає протоколу, і раптом вирішу, що більше не хочу відповідати цьому протоколу. Я успадкував відповідність протоколу, але через те, як Swift працює з init
успадкуванням методу, я не успадкував частину того, що потрібно, щоб відповідати цьому протоколу, і я повинен його реалізувати.
Гаразд, це все має сенс. Але чому я не можу отримати корисніше повідомлення про помилку?
Можливо, повідомлення про помилку може бути більш зрозумілим або кращим, якби в ньому було вказано, що ваш клас більше не відповідає успадкованому NSCoding
протоколу і що для його виправлення вам потрібно реалізувати init(coder: NSCoder)
. Звичайно.
Але Xcode просто не може генерувати це повідомлення, оскільки це насправді не завжди буде справжньою проблемою з не реалізацією або успадкуванням потрібного методу. Існує хоча б ще одна причина зробити init
способи, required
крім відповідності протоколу, і це заводські методи.
Якщо я хочу написати правильний заводський метод, мені потрібно вказати тип повернення, який буде Self
(еквівалент Свіфта об'єктива-C instanceType
). Але для цього мені фактично потрібно використовувати required
метод ініціалізатора.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Це створює помилку:
Конструюючи об’єкт класу типу "Self" зі значенням метатипу, слід використовувати ініціалізатор "необхідний"
Це в основному та сама проблема. Якщо ми підкласи Box
, наші підкласи успадкують метод класу factory
. Тож ми могли б зателефонувати SubclassedBox.factory()
. Однак, без required
ключового слова init(size:)
методу, Box
підкласи не гарантовано успадкують те, self.init(size:)
що factory
викликає.
Таким чином, ми повинні зробити цей метод, required
якщо ми хочемо такий заводський метод, і це означає, що якщо наш клас реалізує такий метод, у нас буде required
метод ініціалізатора, і ми зіткнемося з тими самими проблемами, з якими ви зіткнулися тут з NSCoding
протоколом.
Зрештою, все зводиться до основного розуміння того, що ініціалізатори Swift грають дещо іншим набором правил успадкування, що означає, що ви не гарантовано успадкуєте ініціалізатори від свого суперкласу. Це відбувається тому, що ініціалізатори надкласового класу не можуть знати про ваші нові збережені властивості, і вони не змогли створити ваш об'єкт у дійсному стані. Але з різних причин суперклас може позначати ініціалізатор як required
. Коли це станеться, ми можемо використати один із самих конкретних сценаріїв, за яким насправді наслідуємо required
метод, або ми мусимо його реалізувати самостійно.
Тут головним є те, що якщо ми отримуємо помилку, яку ви бачите тут, це означає, що ваш клас насправді не реалізує метод.
Як, мабуть, один із останніх прикладів того, що підкласи Свіфта не завжди успадковують init
методи своїх батьків (що, на мою думку, є абсолютно важливим для повного розуміння цієї проблеми), розглянемо цей приклад:
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)
Це не вдається зібрати.
Повідомлення про помилку, яке воно дає, є дещо оманливим:
Додатковий аргумент 'b' у виклику
Але справа в тому, що Bar
він не успадковує жоден із Foo
російських init
методів, оскільки він не задовольнив жодного з двох спеціальних випадків для успадкування init
методів від свого батьківського класу.
Якби це «Objective-C», ми б це успадкували init
без проблем, оскільки «Objective-C» цілком задоволений тим, що не ініціалізує властивості об’єктів (хоча, як розробник, ви не повинні були цим задоволені). У Свіфта це просто не зробить. Ви не можете мати недійсний стан, і успадкування ініціалізаторів надкласового класу може призвести лише до недійсних станів об'єкта.
init(collection:MPMediaItemCollection)
. Ви повинні надати справжню колекцію засобів масової інформації; в цьому і полягає суть цього класу. Цей клас просто неможливо встановити без одного. Він збирається проаналізувати колекцію та ініціалізувати десяток змінних екземплярів. У цьому і полягає вся суть цього єдиного і єдиного ініціалізатора! Таким чином,init(coder:)
не має жодного змістовного (або навіть безглуздого) MPMediaItemCollection, який би постачався сюди;fatalError
правильний лише підхід.