Класс не реализует обязательные члены своего суперкласса

138

Итак, я обновился до 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)

    }

}

Итак, мой вопрос: почему я получаю эту ошибку и как ее исправить? Что я не реализую? Я вызываю назначенный инициализатор.

Теги:
sprite-kit

4 ответа

120
Лучший ответ

От сотрудника 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)
}
  • 3
    Однако второй вариант бесполезен в большинстве случаев. Взять, к примеру, мой требуемый инициализатор init(collection:MPMediaItemCollection) . Вы должны предоставить реальную коллекцию медиа-предметов; это точка этого класса. Этот класс просто невозможно создать без него. Он собирается проанализировать коллекцию и инициализировать дюжину переменных экземпляра. В этом вся суть единственного назначенного инициализатора! Таким образом, init(coder:) имеет никакого значимого (или даже бессмысленного) MPMediaItemCollection для предоставления здесь; только подход fatalError является правильным.
  • 0
    @matt Правильно, один или другой вариант будет работать лучше в разных ситуациях.
Показать ещё 3 комментария
59

Есть два абсолютно важных частей информации, зависящей от 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' в вызове.

Изображение 1828

Если это было 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)
    }
}

Это порождает ошибку:

Построение объекта типа класса "Я" с значением метатипа должно использовать "обязательный" инициализатор

Изображение 1829

Это в основном та же проблема. Если мы подклассом 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)

Это не скомпилируется.

Изображение 1830

Сообщение об ошибке, которое оно дает, немного вводит в заблуждение:

Дополнительный аргумент 'b' при вызове

Но точка в том, что Bar не наследует ни один из методов Foo init, потому что не удовлетворил ни один из двух особых случаев для наследования методов init из его родительского класса.

Если это было Objective-C, мы бы не наделили это init без проблем, потому что Objective-C отлично справляется с инициализацией свойств объектов (хотя, как разработчик, вы не должны были быть довольны этим), В Swift это просто не будет. Вы не можете иметь недопустимое состояние, и наследование инициализаторов суперкласса может привести только к недействительным состояниям объектов.

  • 0
    Не могли бы вы объяснить, что означает это предложение, или привести пример? «(и удобные инициализаторы, которые не указывали на инициализаторы, которые мы реализовали)»
  • 0
    @AbbeyJackson stackoverflow.com/a/36612507/2792531
Показать ещё 1 комментарий
56

Почему возникла эта проблема? Ну, очевидно, что это всегда было важно (т.е. В Objective-C, с того дня, как я начал программировать Cocoa в Mac OS X 10.0), чтобы иметь дело с инициализаторами, которые ваш класс не готов обрабатывать. В документах всегда были четко указаны ваши обязанности в этом отношении. Но сколько из нас потрудилось выполнить их, полностью и в письме? Наверное, никто из нас! И компилятор не применял их; все было чисто условно.

Например, в моем подклассе контроллера представления Objective-C с этим назначенным инициализатором:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

... очень важно, чтобы нам передали фактическую коллекцию элементов мультимедиа: экземпляр просто не может возникнуть без него. Но я не написал "пробку", чтобы кто-то не инициализировал меня голыми костями init. Я должен был написать один (собственно говоря, я должен был написать реализацию initWithNibName:bundle:, унаследованного назначенного инициализатора); но я был слишком ленив, чтобы беспокоиться, потому что я "знал", что я никогда не ошибочно не инициализировал свой класс таким образом. Это оставило зияющую дыру. В Objective-C кто-то может вызывать голые кости init, оставляя мои иварины неинициализированными, и мы поднимаемся на ручей без весла.

Быстро, чудесно, спасает меня от себя в большинстве случаев. Как только я перевел это приложение в Свифт, вся проблема исчезла. Swift эффективно создает пробку для меня! Если init(collection:MPMediaItemCollection) - единственный назначенный инициализатор, объявленный в моем классе, я не могу быть инициализирован вызовом bare-bones init(). Это чудо!

То, что произошло в семени 5, просто состоит в том, что компилятор понял, что чудо не работает в случае init(coder:), потому что теоретически экземпляр этого класса может исходить от nib, а компилятор не может предотвратите это - и когда загрузится наконечник, init(coder:). Поэтому компилятор заставляет вас писать пробку явно. И совершенно верно.

  • 0
    Спасибо за такой подробный ответ. Это действительно приносит свет в дело.
  • 0
    Возвышение к pasta12 за то, что он сказал мне, как заставить компилятор замолчать, но также и вам за то, что вы поняли, о чем он скулил.
Показать ещё 4 комментария
31

добавить

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}
  • 3
    Это работает, но я не думаю, что это ошибка. инициализаторы не наследуются в swift (когда объявлен ваш собственный инициализатор), и это помечается обязательным ключевым словом. Единственная проблема заключается в том, что теперь мне нужно инициализировать ВСЕ мои свойства в этом методе для каждого из моих классов, что будет представлять собой много потраченного впустую кода, так как я его вообще не использую. Или мне придется объявить все мои свойства как неявно развернутые необязательные типы, чтобы обойти инициализацию, что я тоже не хочу делать.
  • 1
    Ага! Как только я сказал, что это ошибка, я понял, что это действительно логично. Я согласен, что это будет много потраченного впустую кода, поскольку, как и вы, я бы никогда не использовал этот метод init. Еще не уверен насчет элегантного решения
Показать ещё 8 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню