Класс не реализует обязательные члены своего суперкласса
поэтому я обновился до 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)
}
}
Итак, мой вопрос, почему я получаю эту ошибку и как я могу это исправить? Что это то, что я не реализую? Я вызываю назначенный инициализатор.
4 ответа:
от сотрудника Apple на форумах разработчиков:
" способ объявить компилятору и встроенной программе, что вы действительно не хочу быть NSCoding-совместимым, чтобы сделать что-то вроде этого:"
required init(coder: NSCoder) { fatalError("NSCoding not supported") }
если вы знаете, что не хотите быть совместимым с NSCoding, это вариант. Я использовал этот подход с большим количеством моего кода SpriteKit, поскольку я знаю, что не буду загружать его из раскадровки.
другой вариант вы можете возьмите, что работает достаточно хорошо, чтобы реализовать метод в качестве удобства init, например:
convenience required init(coder: NSCoder) { self.init(stringParam: "", intParam: 5) }
обратите внимание на вызов инициализатора в
self
. Это позволяет использовать только фиктивные значения для параметров, в отличие от всех необязательных свойств, избегая при этом возникновения фатальной ошибки.
третий вариант, конечно, заключается в реализации метода при вызове super и инициализации всех ваших необязательных свойств. Вы должны принять этот подход, если объект-это представление, загружаемое из раскадровки:
required init(coder aDecoder: NSCoder!) { foo = "some string" bar = 9001 super.init(coder: aDecoder) }
есть два абсолютно важно части Swift-специфической информации, которые отсутствуют в существующих ответах, которые, я думаю, помогут полностью прояснить это.
- если протокол определяет инициализатор как обязательный метод, этот инициализатор должен быть отмечен с помощью Swift
required
ключевое слово.- Swift имеет специальный набор правил наследования в отношении
init
методы.The tl; dr это это:
если вы реализуете какие-либо инициализаторы, вы больше не наследуете ни один из назначенных инициализаторов суперкласса.
единственные инициализаторы, если таковые имеются, которые вы унаследуете, являются инициализаторами удобства суперкласса, которые указывают на назначенный инициализатор, который вы случайно переопределили.
так... готовы к длинной версии?
Swift имеет специальный набор правил наследования относительно
init
методы.я знаю, что это был второй из двух пунктов, которые я перечислил, но мы не можем понять первый пункт, или почему
required
ключевое слово даже существует, пока мы не поймем этот момент. Как только мы понимаем этот момент, другой становится довольно очевидным.вся информация, которую я рассматриваю в этом разделе этого ответа, взята из документации Appleздесь.
от яблони документы:
в отличие от подклассов в Objective-C, подклассы Swift не наследуют свои инициализаторы суперкласса по умолчанию. подход Swift предотвращает ситуацию, в которой простой инициализатор из суперкласса наследуется более специализированным подклассом и используется для создания нового экземпляра подкласса, который не полностью или правильно инициализирован.
выделено мной.
так, прямо из Apple документы прямо там, мы видим, что Swift подклассы не всегда (и обычно не) наследуют их суперкласс
init
методы.Итак, когда они наследуют от суперкласса?
есть два правила, которые определяют, когда подкласс наследует
init
методы от своих родителей. Из документов Apple:Правило 1
если ваш подкласс не определяет никаких назначенных инициализаторов, он автоматически наследует все назначенные инициализаторы суперкласса.
Правило 2
если ваш подкласс предоставляет реализацию всех своих назначенных инициализаторов суперкласса - либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части его определения-тогда он автоматически наследует все инициализаторы удобства суперкласса.
Правило 2 не имеет к этому особого отношения разговор, потому что
SKSpriteNode
' sinit(coder: NSCoder)
вряд ли будет удобным методом.Итак, ваша
InfoBar
класс наследуетrequired
инициализатор вплоть до точки, которую вы добавилиinit(team: Team, size: CGSize)
.если бы вы не предоставили это
init
метод и вместо этого сделал свойInfoBar
добавленные свойства необязательны или предоставляют им значения по умолчанию, тогда вы все равно наследуетеSKSpriteNode
' sinit(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 ' в вызове.
если бы это было объективно-C, это было бы нет проблем с наследованием. Если мы инициализировали a
Bar
СinitWithFoo:
в Objective-C, theself.bar
свойство будет простоnil
. Это, наверное, не здорово, но это совершенно действительный состояние объекта, в котором он находится. Это не совершенно допустимое состояние для объекта Swift.self.bar
не является обязательным и не может бытьnil
.опять же, единственный способ наследовать инициализаторы-это не предоставлять свои собственные. Поэтому, если мы попытаемся наследовать удаление
Bar
' sinit(foo: String, bar: String)
, например:class Bar: Foo { var bar: String }
теперь мы возвращаемся к наследованию (вроде), но это не будет компилироваться... и сообщение об ошибке объясняет точно, почему мы не наследуем суперкласс
init
методы:вопрос: класс ' Bar ' не имеет инициализаторов
Fix-It: сохраненное свойство ' bar ' без инициализаторов предотвращает синтезированные инициализаторы
если мы добавили хранить свойства в нашем подклассе нет никакого возможного быстрого способа создать действительный экземпляр нашего подкласса с инициализаторами суперкласса, которые не могли бы знать о сохраненных свойствах нашего подкласса.
хорошо, почему я должен выполнять
init(coder: NSCoder)
на всех? Почему этоrequired
?Свифт
init
методы могут играть по специальному набору правил наследования, но соответствие протокола по-прежнему наследуется по цепочке. Если родительский класс соответствует протокол, его подклассы должны соответствовать этому протоколу.однако, помните, Свифт
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
кроме соответствия протокола, и это фабрика методы.если я хочу написать правильный заводской метод, мне нужно указать тип возвращаемого значения
Self
(эквивалент SWIFT в Objective-С И Л!--47-->). Но для того, чтобы сделать это, мне нужно использовать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
подклассы не гарантированно наследуют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 ' in звоните
, но и
Bar
не наследует ни одного изFoo
' sinit
методы, потому что он не удовлетворил ни один из двух особых случаях наследованияinit
методы из родительского класса.если бы это было Objective-C, мы бы унаследовали это
init
без проблем, потому что Objective-C совершенно счастлив не инициализировать свойства объектов (хотя, как разработчик, вы не должны были быть довольны этим). В Swift этого просто не будет делать. Недопустимое состояние невозможно, а наследование инициализаторов суперкласса может привести только к недопустимым состояниям объекта.
почему возникла эта проблема? Ну, факт в том, что он имеет всегда было важно (т. е. в Objective-C, с того дня, как я начал программировать Cocoa обратно в Mac OS X 10.0) иметь дело с инициализаторами, которые ваш класс не готов обрабатывать. Документы всегда были достаточно ясны о ваших обязанностях в этом отношении. Но многие ли из нас потрудились выполнить их полностью и в точности? Наверное, никто из нас! И компилятор не принуждал их; это было все чисто условно.
например, в моем подклассе контроллера вида Objective-C с этим назначенным инициализатором:
- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;
...крайне важно, чтобы мы передали фактическую коллекцию элементов мультимедиа: экземпляр просто не может существовать без него. Но я не написал никакой "пробки", чтобы помешать кому-то инициализировать меня с голыми костями
init
вместо. Я должны написал один (на самом деле, собственно говоря, я должен был написать реализацияinitWithNibName:bundle:
, унаследованный назначенный инициализатор); но я был слишком ленив, чтобы беспокоиться, потому что я "знал", что никогда не буду неправильно инициализировать свой собственный класс таким образом. Это оставило зияющую дыру. В объектив-с, кто-то можете позвоните голые костиinit
, оставив мой Ивар неинициализированным, и мы поднимаемся вверх по ручью без весла.Свифт, чудесно, спасает меня от самого себя в большинстве случаев. Как только я перевел это приложение в Swift, вся проблема ушла. Быстрый эффективно создает пробку для меня! Если
init(collection:MPMediaItemCollection)
является единственным назначенным инициализатором, объявленным в моем классе, я не могу быть инициализирован вызовом bare-bonesinit()
. Это же чудо!то, что произошло в seed 5, - это просто то, что компилятор понял, что чудо не работает в случае
init(coder:)
, потому что теоретически экземпляр этого класса может исходить из пера, и компилятор не может этого предотвратить - и когда перо загружается,init(coder:)
будет называться. Так что компилятор делает вас напишите стопор явно. И совершенно правильно.