Поэтому сегодня я обновился до Xcode 6 beta 5 и заметил, что получил ошибки почти во всех моих подклассах классов Apple.
Ошибка гласит:
Класс 'x' не реализует обязательные члены своего суперкласса
Вот один пример, который я выбрал, потому что этот класс в настоящее время довольно легкий, поэтому его будет легко опубликовать.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Итак, мой вопрос: почему я получаю эту ошибку и как я могу ее исправить? Что я не реализую? Я звоню назначенному инициализатору.
источник
init(collection:MPMediaItemCollection)
. Вы должны предоставить реальную коллекцию медиа-предметов; это точка этого класса. Этот класс просто невозможно создать без него. Он собирается проанализировать коллекцию и инициализировать дюжину переменных экземпляра. В этом вся суть единственного назначенного инициализатора! Таким образом, неinit(coder:)
имеет никакого значимого (или даже бессмысленного) MPMediaItemCollection для предоставления здесь; толькоfatalError
подход правильный.init(collection:MPMediaItemCollection!)
. Это позволилоinit(coder:)
бы пройти ноль. Но потом я понял: нет, теперь вы просто дурачите компилятор. Прохождение nil недопустимо, поэтому бросайтеfatalError
и двигайтесь дальше. :)Существуют две абсолютно важные части специфической для Swift информации, которые отсутствуют в существующих ответах, и я думаю, что это поможет прояснить это полностью.
required
ключевого слова Swift .init
методов.Т.Л., д - р это:
Единственные инициализаторы, если таковые имеются, которые вы унаследуете, это удобные инициализаторы суперкласса, которые указывают на назначенный инициализатор, который вы случайно переопределили.
Итак ... готов к длинной версии?
Swift имеет специальный набор правил наследования, касающихся
init
методов.Я знаю, что это был второй из двух моментов, которые я сделал, но мы не можем понять первый пункт, или почему
required
ключевое слово вообще существует, пока мы не поймем этот момент. Как только мы поймем этот момент, другой станет довольно очевидным.Вся информация, которую я рассматриваю в этом разделе этого ответа, взята из документации Apple, найденной здесь .
Из документов Apple:
Акцент мой.
Итак, прямо из документации Apple прямо здесь мы видим, что подклассы Swift не всегда (и обычно не) наследуют
init
методы своего суперкласса .Итак, когда они наследуют от своего суперкласса?
Есть два правила, которые определяют, когда подкласс наследует
init
методы от своего родителя. Из документов Apple:Правило 2 не имеет особого значения для этого разговора , потому что
SKSpriteNode
«sinit(coder: NSCoder)
вряд ли будет удобный метод.Итак, ваш
InfoBar
класс наследовалrequired
инициализатор вплоть до добавленной вами точкиinit(team: Team, size: CGSize)
.Если бы вы не предоставили этот
init
метод , и вместо этого сделали свойInfoBar
«ы добавлены свойства по желанию или при условии их значения по умолчанию, то вы бы до сих пор был унаследоватьSKSpriteNode
» Sinit(coder: NSCoder)
. Однако, когда мы добавили наш собственный пользовательский инициализатор, мы перестали наследовать назначенные инициализаторы нашего суперкласса (и удобные инициализаторы, которые не указывали на инициализаторы, которые мы реализовали).Итак, в качестве упрощенного примера я представляю это:
Который представляет следующую ошибку:
Если бы это был Objective-C, у него не было бы проблем с наследованием. Если бы мы инициализировали
Bar
сinitWithFoo:
в Objective-C,self.bar
свойство было бы простоnil
. Это, вероятно, не очень хорошо, но это совершенно правильное состояние для объекта, в котором он находится. Это не совсем корректное состояние для объекта Swift.self.bar
Не является необязательным и не может бытьnil
.Опять же, единственный способ, которым мы наследуем инициализаторы, это не предоставляя свои собственные. Так что, если мы попытаемся наследовать, удалив
Bar
sinit(foo: String, bar: String)
, как таковой:Теперь мы вернулись к наследованию (вроде), но это не скомпилируется ... и сообщение об ошибке объясняет, почему мы не наследуем
init
методы суперкласса :Если мы добавили хранимые свойства в наш подкласс, то у Swift нет возможности создать действительный экземпляр нашего подкласса с инициализаторами суперкласса, которые не могут знать о сохраненных свойствах нашего подкласса.
Ладно, ну а зачем мне вообще это реализовывать
init(coder: NSCoder)
? Почему этоrequired
?init
Методы Swift могут играть по специальному набору правил наследования, но соответствие протоколу все еще наследуется по цепочке. Если родительский класс соответствует протоколу, его подклассы должны соответствовать этому протоколу.Обычно это не проблема, поскольку большинству протоколов требуются только методы, которые не воспроизводятся по специальным правилам наследования в Swift, поэтому, если вы наследуете от класса, соответствующего протоколу, вы также наследуете все методы или свойства, которые позволяют классу удовлетворять требованиям протокола.
Однако помните, что
init
методы Swift играют по специальному набору правил и не всегда наследуются. Из-за этого класс, который соответствует протоколу, который требует специальныхinit
методов (таких какNSCoding
), требует, чтобы класс пометил этиinit
методы какrequired
.Рассмотрим этот пример:
Это не компилируется. Он генерирует следующее предупреждение:
Он хочет, чтобы я сделал
init(foo: Int)
инициализатор требуется. Я мог бы также сделать это счастливым, создав классfinal
(то есть класс не может быть унаследован от).Итак, что произойдет, если я подкласс? С этого момента, если я подкласс, я в порядке. Если я добавлю инициализаторы, я вдруг перестану наследовать
init(foo:)
. Это проблематично, потому что теперь я больше не соответствуюInitProtocol
. Я не могу создать подкласс из класса, который соответствует протоколу, а затем внезапно решить, что я больше не хочу соответствовать этому протоколу. Я унаследовал соответствие протокола, но из-за того, как Swift работает сinit
наследованием метода, я не унаследовал часть того, что требуется для соответствия этому протоколу, и я должен его реализовать.Хорошо, это все имеет смысл. Но почему я не могу получить более полезное сообщение об ошибке?
Возможно, сообщение об ошибке могло бы быть более ясным или лучшим, если бы в нем указывалось, что ваш класс больше не соответствует унаследованному
NSCoding
протоколу и что для его исправления необходимо реализоватьinit(coder: NSCoder)
. Конечно.Но Xcode просто не может сгенерировать это сообщение, потому что на самом деле это не всегда будет реальной проблемой, если не реализовать или унаследовать требуемый метод. Существует, по крайней мере, еще одна причина для создания
init
методовrequired
помимо соответствия протокола, и это заводские методы.Если я хочу написать правильный фабричный метод, мне нужно указать тип возвращаемого значения
Self
(эквивалент Swift Objective-CinstanceType
). Но для этого мне нужно использоватьrequired
метод инициализатора.Это генерирует ошибку:
Это в основном та же проблема. Если мы создадим подкласс
Box
, наши подклассы унаследуют метод классаfactory
. Так что мы могли бы позвонитьSubclassedBox.factory()
. Тем не менее, безrequired
ключевого слова наinit(size:)
методе,Box
подклассы «s не гарантируется наследуйте ,self.init(size:)
чтоfactory
звонит.Поэтому мы должны создать этот метод,
required
если нам нужен такой фабричный метод, а это значит, что если наш класс реализует такой метод, у нас будетrequired
метод инициализатора, и мы столкнемся с точно такими же проблемами, с которыми вы столкнулись здесь сNSCoding
протоколом.В конечном итоге все сводится к базовому пониманию того, что инициализаторы Swift играют по несколько иному набору правил наследования, что означает, что вы не гарантированно наследуете инициализаторы от своего суперкласса. Это происходит потому, что инициализаторы суперкласса не могут знать о ваших новых сохраненных свойствах и не могут создать экземпляр вашего объекта в допустимом состоянии. Но по разным причинам суперкласс может пометить инициализатор как
required
. Когда это происходит, мы можем либо использовать один из очень специфических сценариев, по которому мы действительно наследуемrequired
метод, либо мы должны реализовать его самостоятельно.Главное здесь - то, что если мы получаем ошибку, которую вы видите здесь, это означает, что ваш класс фактически не реализует метод вообще.
В качестве, возможно, одного из последних примеров того, что подклассы Swift не всегда наследуют
init
методы их родителей (что, я думаю, является абсолютно важным для полного понимания этой проблемы), рассмотрим этот пример:Это не в состоянии скомпилировать.
Это сообщение об ошибке немного вводит в заблуждение:
Но дело в том,
Bar
не наследует любого изFoo
«sinit
методов , так как он не удовлетворен либо из двух специальных случаев для наследованияinit
методов из родительского класса.Если бы это был Objective-C, мы бы унаследовали это
init
без проблем, потому что Objective-C совершенно счастлив, не инициализируя свойства объектов (хотя как разработчик, вы не должны были бы быть довольны этим). В Swift это просто не подойдет. Вы не можете иметь недопустимое состояние, и наследование инициализаторов суперкласса может привести только к недопустимым состояниям объекта.источник
Почему возникла эта проблема? Что ж, очевидный факт заключается в том, что всегда было важно (то есть в Objective-C, начиная со дня, когда я начал программировать Cocoa в Mac OS X 10.0) иметь дело с инициализаторами, которые ваш класс не готов обработать. В документах всегда было достаточно ясно о ваших обязанностях в этом отношении. Но сколько из нас удосужились выполнить их, полностью и до буквы? Наверное, никто из нас! И компилятор не применял их; все было чисто условно.
Например, в моем подклассе контроллера представления Objective C с этим назначенным инициализатором:
... очень важно, чтобы нам передали фактическую коллекцию медиа-элементов: экземпляр просто не может существовать без него. Но я не написал «стопор», чтобы не дать кому-то инициализировать меня голыми руками
init
. Я должен был написать один (на самом деле, собственно говоря, я должен был написать реализациюinitWithNibName:bundle:
наследуемого назначенного инициализатора); но мне было лень беспокоиться, потому что я «знал», что никогда не буду неправильно инициализировать свой собственный класс таким образом. Это оставило зияющую дыру. В Objective-C кто-то может назвать голымиinit
, оставив мои ивары неинициализированными, и мы бездельничаем.Стремительно, удивительно, спасает меня от себя в большинстве случаев. Как только я перевел это приложение на Swift, вся проблема ушла. Свифт эффективно создает пробку для меня! Если
init(collection:MPMediaItemCollection)
в моем классе объявлен единственный инициализатор, я не могу быть инициализирован вызовом bare-bonesinit()
. Это чудо!В семени 5 произошло просто то, что компилятор понял, что чудо не работает в случае
init(coder:)
, потому что в теории экземпляр этого класса может исходить из кончика, и компилятор не может этого предотвратить - и когда nib загружается,init(coder:)
будет вызываться. Таким образом, компилятор заставляет вас писать пробку явно. И совершенно верно тоже.источник
fatalError
ограничитель, описанный в stackoverflow.com/a/25128815/341994 . Просто сделайте его фрагментом кода пользователя, и теперь вы можете просто добавить его туда, где это необходимо. Занимает полсекунды.Добавить
источник