Итак, я обновился до Xcode 6 beta 5 сегодня и заметил, что получил почти все мои подклассы классов Apple.
Вот один из примеров, который я выбрал, потому что этот класс в настоящее время довольно легкий, поэтому его легко разместить.
Итак, мой вопрос: почему я получаю эту ошибку и как ее исправить? Что я не реализую? Я вызываю назначенный инициализатор.
Ответ 2
Есть два абсолютно важных частей информации, зависящей от Swift, которые отсутствуют в существующих ответах, которые, как мне кажется, полностью устраняют это.
- Если протокол указывает инициализатор как необходимый метод, этот инициализатор должен быть отмечен с помощью слова Swift
required
.
- Swift имеет специальный набор правил наследования в отношении методов
init
.
tl; dr:
Если вы реализуете какие-либо инициализаторы, вы больше не наследуете ни один из инициализаторов, назначенных суперклассам.
Единственные инициализаторы, если таковые имеются, которые вы наследуете, являются инициализаторами удобства супер класса, которые указывают на назначенный инициализатор, который вы случайно переопределили.
Итак... готов к длинной версии?
Swift имеет специальный набор правил наследования в отношении методов init
.
Я знаю, что это был второй из двух пунктов, которые я сделал, но мы не можем понять первый момент или почему ключевое слово required
существует даже до тех пор, пока мы не поймем эту точку. Как только мы поймем этот момент, другой становится довольно очевидным.
Вся информация, которую я расскажу в этом разделе этого ответа, приведена в документации Apple, найденной здесь.
Из документов Apple:
В отличие от подклассов в Objective-C подклассы Swift по умолчанию не наследуют свои инициализаторы суперкласса. Подход Swifts предотвращает ситуацию, когда простой инициализатор из суперкласса наследуется более специализированным подклассом и используется для создания нового экземпляра подкласса, который не полностью или правильно инициализирован.
Подчеркните мой.
Итак, прямо из документов Apple прямо там, мы видим, что подклассы Swift не всегда (и, как правило, не находят) наследуют их методы суперкласса init
.
Итак, когда они наследуют свой суперкласс?
Существует два правила, определяющие, когда подкласс наследует методы init
от его родителя. Из документов Apple:
Правило 1
Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.
Правило 2
Если ваш подкласс обеспечивает реализацию всех его инициализаторов, назначенных суперклассам, либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части своего определения, то он автоматически наследует все инициализаторы удобства суперкласса.
Правило 2 не имеет особого отношения к этому разговору, поскольку SKSpriteNode
init(coder: NSCoder)
вряд ли будет удобным методом.
Итак, ваш класс InfoBar
наследовал инициализатор required
до момента добавления init(team: Team, size: CGSize)
.
Если бы вы не предоставили этот метод init
и вместо этого добавили свои добавленные свойства InfoBar
или предоставили им значения по умолчанию, то вы все равно наследовали бы SKSpriteNode
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
init(foo: String, bar: String)
, как таковой:
class Bar: Foo {
var bar: String
}
Теперь мы вернемся к наследованию (вроде), но это не скомпилируется... и сообщение об ошибке объясняет, почему мы не наследуем методы суперкласса init
:
Проблема: Класс "Bar" не имеет инициализаторов
Fix-It: Хранилище "bar" без инициализаторов предотвращает синтезированные инициализаторы
Если мы добавили сохраненные свойства в наш подкласс, не существует способа Быстрого создания действительного экземпляра нашего подкласса с инициализаторами суперкласса, которые не могли бы знать о наших хранимых свойствах подкласса.
Хорошо, хорошо, зачем мне вообще выполнять init(coder: NSCoder)
? Почему это required
?
Методы Swift init
могут воспроизводиться с помощью специального набора правил наследования, но соответствие протокола по-прежнему унаследовано по цепочке. Если родительский класс соответствует протоколу, его подклассы должны соответствовать этому протоколу.
Обычно это не проблема, потому что большинство протоколов требуют только методы, которые не играют специальные правила наследования в Swift, поэтому, если вы наследуете класс, соответствующий протоколу, вы также наследуете все методы или свойства, которые позволяют классу удовлетворять требованиям протокола.
Однако помните, что методы 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"
Fix-It: Вставьте необходимые
Он хочет, чтобы я сделал инициализатор init(foo: Int)
. Я также мог бы сделать это счастливым, сделав класс final
(что означает, что класс не может быть унаследован).
Итак, что произойдет, если я подкласс? С этого момента, если я подкласс, я в порядке. Если я добавлю какие-либо инициализаторы, я вдруг перестаю наследовать init(foo:)
. Это проблематично, потому что теперь я больше не согласен с InitProtocol
. Я не могу подкласса из класса, который соответствует протоколу, а затем вдруг решил, что я больше не хочу соответствовать этому протоколу. Я унаследовал соответствие протокола, но из-за того, как Swift работает с наследованием метода init
, я не унаследовал часть того, что требуется для соответствия этому протоколу, и я должен его реализовать.
Хорошо, все это имеет смысл. Но почему я не могу получить более полезное сообщение об ошибке?
Возможно, сообщение об ошибке может быть более ясным или лучше, если он указал, что ваш класс больше не соответствует унаследованному протоколу NSCoding
, и для его исправления вам необходимо реализовать init(coder: NSCoder)
. Конечно.
Но Xcode просто не может сгенерировать это сообщение, потому что на самом деле это не всегда будет актуальной проблемой, не реализуя или не наследуя требуемый метод. Существует, по крайней мере, еще одна причина сделать методы init
required
помимо соответствия протокола и методы factory.
Если я хочу написать правильный метод factory, мне нужно указать тип возврата как 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)
}
}
Это порождает ошибку:
Построение объекта типа класса "Я" с значением метатипа должно использовать "обязательный" инициализатор
![введите описание изображения здесь]()
Это в основном та же проблема. Если мы подклассом Box
, наши подклассы наследуют метод класса factory
. Таким образом, мы могли бы назвать SubclassedBox.factory()
. Однако без ключевого слова required
в методе init(size:)
подклассы Box
не гарантируют наследование self.init(size:)
, вызываемого factory
.
Итак, мы должны сделать этот метод required
, если нам нужен метод factory, подобный этому, и это означает, что если наш класс реализует такой метод, у нас будет метод инициализации 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
init
, потому что не удовлетворил ни один из двух особых случаев для наследования методов init
из его родительского класса.
Если это было Objective-C, мы бы не наделили это init
без проблем, потому что Objective-C отлично справляется с инициализацией свойств объектов (хотя, как разработчик, вы не должны были быть довольны этим), В Swift это просто не будет. Вы не можете иметь недопустимое состояние, и наследование инициализаторов суперкласса может привести только к недействительным состояниям объектов.