Существуют две абсолютно важные части специфической для Swift информации, которые отсутствуют в существующих ответах, и я думаю, что это поможет прояснить это полностью.
- Если протокол указывает инициализатор как обязательный метод, этот инициализатор должен быть помечен с использованием
required
ключевого слова Swift .
- Swift имеет специальный набор правил наследования, касающихся
init
методов.
Т.Л., д - р это:
Если вы реализуете какие-либо инициализаторы, вы больше не наследуете ни один из назначенных инициализаторов суперкласса.
Единственные инициализаторы, если таковые имеются, которые вы унаследуете, это удобные инициализаторы суперкласса, которые указывают на назначенный инициализатор, который вы случайно переопределили.
Итак ... готов к длинной версии?
Swift имеет специальный набор правил наследования, касающихся init
методов.
Я знаю, что это был второй из двух моментов, которые я сделал, но мы не можем понять первый пункт, или почему required
ключевое слово вообще существует, пока мы не поймем этот момент. Как только мы поймем этот момент, другой станет довольно очевидным.
Вся информация, которую я рассматриваю в этом разделе этого ответа, взята из документации Apple, найденной здесь .
Из документов Apple:
В отличие от подклассов в Objective-C, подклассы Swift по умолчанию не наследуют свои инициализаторы суперкласса. Подход Swift предотвращает ситуацию, в которой простой инициализатор из суперкласса наследуется более специализированным подклассом и используется для создания нового экземпляра подкласса, который не полностью или правильно инициализирован.
Акцент мой.
Итак, прямо из документации Apple прямо здесь мы видим, что подклассы Swift не всегда (и обычно не) наследуют 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, у него не было бы проблем с наследованием. Если бы мы инициализировали 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' без инициализаторов предотвращает синтезированные инициализаторы
Если мы добавили хранимые свойства в наш подкласс, то у Swift нет возможности создать действительный экземпляр нашего подкласса с инициализаторами суперкласса, которые не могут знать о сохраненных свойствах нашего подкласса.
Ладно, ну а зачем мне вообще это реализовывать init(coder: NSCoder)
? Почему это required
?
init
Методы Swift могут играть по специальному набору правил наследования, но соответствие протоколу все еще наследуется по цепочке. Если родительский класс соответствует протоколу, его подклассы должны соответствовать этому протоколу.
Обычно это не проблема, поскольку большинству протоколов требуются только методы, которые не воспроизводятся по специальным правилам наследования в Swift, поэтому, если вы наследуете от класса, соответствующего протоколу, вы также наследуете все методы или свойства, которые позволяют классу удовлетворять требованиям протокола.
Однако помните, что init
методы Swift играют по специальному набору правил и не всегда наследуются. Из-за этого класс, который соответствует протоколу, который требует специальных init
методов (таких как NSCoding
), требует, чтобы класс пометил эти init
методы как required
.
Рассмотрим этот пример:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Это не компилируется. Он генерирует следующее предупреждение:
Проблема: Требование инициализатора 'init (foo :)' может быть удовлетворено только 'обязательным' инициализатором в не финальном классе 'ConformingClass'
Fix-It: требуется вставить
Он хочет, чтобы я сделал init(foo: Int)
инициализатор требуется. Я мог бы также сделать это счастливым, создав класс final
(то есть класс не может быть унаследован от).
Итак, что произойдет, если я подкласс? С этого момента, если я подкласс, я в порядке. Если я добавлю инициализаторы, я вдруг перестану наследовать init(foo:)
. Это проблематично, потому что теперь я больше не соответствую InitProtocol
. Я не могу создать подкласс из класса, который соответствует протоколу, а затем внезапно решить, что я больше не хочу соответствовать этому протоколу. Я унаследовал соответствие протокола, но из-за того, как Swift работает с init
наследованием метода, я не унаследовал часть того, что требуется для соответствия этому протоколу, и я должен его реализовать.
Хорошо, это все имеет смысл. Но почему я не могу получить более полезное сообщение об ошибке?
Возможно, сообщение об ошибке могло бы быть более ясным или лучшим, если бы в нем указывалось, что ваш класс больше не соответствует унаследованному NSCoding
протоколу и что для его исправления необходимо реализовать init(coder: NSCoder)
. Конечно.
Но Xcode просто не может сгенерировать это сообщение, потому что на самом деле это не всегда будет реальной проблемой, если не реализовать или унаследовать требуемый метод. Существует, по крайней мере, еще одна причина для создания init
методов required
помимо соответствия протокола, и это заводские методы.
Если я хочу написать правильный фабричный метод, мне нужно указать тип возвращаемого значения Self
(эквивалент Swift Objective-C instanceType
). Но для этого мне нужно использовать required
метод инициализатора.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Это генерирует ошибку:
Для создания объекта типа «Self» со значением метатипа необходимо использовать инициализатор required
Это в основном та же проблема. Если мы создадим подкласс Box
, наши подклассы унаследуют метод класса factory
. Так что мы могли бы позвонить SubclassedBox.factory()
. Тем не менее, без required
ключевого слова на init(size:)
методе, Box
подклассы «s не гарантируется наследуйте , self.init(size:)
что factory
звонит.
Поэтому мы должны создать этот метод, required
если нам нужен такой фабричный метод, а это значит, что если наш класс реализует такой метод, у нас будет required
метод инициализатора, и мы столкнемся с точно такими же проблемами, с которыми вы столкнулись здесь с NSCoding
протоколом.
В конечном итоге все сводится к базовому пониманию того, что инициализаторы Swift играют по несколько иному набору правил наследования, что означает, что вы не гарантированно наследуете инициализаторы от своего суперкласса. Это происходит потому, что инициализаторы суперкласса не могут знать о ваших новых сохраненных свойствах и не могут создать экземпляр вашего объекта в допустимом состоянии. Но по разным причинам суперкласс может пометить инициализатор как required
. Когда это происходит, мы можем либо использовать один из очень специфических сценариев, по которому мы действительно наследуем required
метод, либо мы должны реализовать его самостоятельно.
Главное здесь - то, что если мы получаем ошибку, которую вы видите здесь, это означает, что ваш класс фактически не реализует метод вообще.
В качестве, возможно, одного из последних примеров того, что подклассы Swift не всегда наследуют 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
«s init
методов , так как он не удовлетворен либо из двух специальных случаев для наследования init
методов из родительского класса.
Если бы это был Objective-C, мы бы унаследовали это init
без проблем, потому что Objective-C совершенно счастлив, не инициализируя свойства объектов (хотя как разработчик, вы не должны были бы быть довольны этим). В Swift это просто не подойдет. Вы не можете иметь недопустимое состояние, и наследование инициализаторов суперкласса может привести только к недопустимым состояниям объекта.
init(collection:MPMediaItemCollection)
. Вы должны предоставить реальную коллекцию медиа-предметов; это точка этого класса. Этот класс просто невозможно создать без него. Он собирается проанализировать коллекцию и инициализировать дюжину переменных экземпляра. В этом вся суть единственного назначенного инициализатора! Таким образом, неinit(coder:)
имеет никакого значимого (или даже бессмысленного) MPMediaItemCollection для предоставления здесь; толькоfatalError
подход правильный.