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


поэтому я обновился до 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 150

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-специфической информации, которые отсутствуют в существующих ответах, которые, я думаю, помогут полностью прояснить это.

  1. если протокол определяет инициализатор как обязательный метод, этот инициализатор должен быть отмечен с помощью Swift required ключевое слово.
  2. Swift имеет специальный набор правил наследования в отношении init методы.

The tl; dr это это:

если вы реализуете какие-либо инициализаторы, вы больше не наследуете ни один из назначенных инициализаторов суперкласса.

единственные инициализаторы, если таковые имеются, которые вы унаследуете, являются инициализаторами удобства суперкласса, которые указывают на назначенный инициализатор, который вы случайно переопределили.

так... готовы к длинной версии?


Swift имеет специальный набор правил наследования относительно init методы.

я знаю, что это был второй из двух пунктов, которые я перечислил, но мы не можем понять первый пункт, или почему required ключевое слово даже существует, пока мы не поймем этот момент. Как только мы понимаем этот момент, другой становится довольно очевидным.

вся информация, которую я рассматриваю в этом разделе этого ответа, взята из документации 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 ' в вызове.

enter image description here

если бы это было объективно-C, это было бы нет проблем с наследованием. Если мы инициализировали a Bar С initWithFoo: в Objective-C, the 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 ' без инициализаторов предотвращает синтезированные инициализаторы

если мы добавили хранить свойства в нашем подклассе нет никакого возможного быстрого способа создать действительный экземпляр нашего подкласса с инициализаторами суперкласса, которые не могли бы знать о сохраненных свойствах нашего подкласса.


хорошо, почему я должен выполнять 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' инициализатор

enter image description here

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

это не удается скомпилировать.

enter image description here

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

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

, но и Bar не наследует ни одного из Foo ' s init методы, потому что он не удовлетворил ни один из двух особых случаях наследования 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-bones init(). Это же чудо!

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

добавить

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}