クラスはスーパークラスの必要なメンバーを実装していません


155

そのため、今日Xcode 6ベータ5に更新しましたが、Appleのクラスのほとんどすべてのサブクラスでエラーが発生することに気付きました。

エラーの状態:

クラス 'x'は、スーパークラスの必要なメンバーを実装していません

このクラスは現在非常に軽量なので、投稿が簡単であるため、私が選んだ1つの例を示します。

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)

    }

}

だから私の質問は、なぜこのエラーが発生するのですか、どうすれば修正できますか?実装していないのは何ですか?指定イニシャライザを呼び出しています。

回答:


127

開発者フォーラムのアップル社員から:

「コンパイラーおよびビルドされたプログラムに対して、NSCoding互換にしたくないことを宣言する方法は、次のようにすることです。」

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

NSCodingに準拠したくない場合は、これがオプションです。ストーリーボードから読み込まないので、SpriteKitコードの多くでこのアプローチを採用しました。


あなたが取ることができる別のオプションは、次のように、便利なinitとしてメソッドを実装することです:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

での初期化子の呼び出しに注意してくださいself。これにより、致命的なエラーのスローを回避しながら、すべてのオプションではないプロパティとは対照的に、パラメーターにダミー値を使用するだけで済みます。


もちろん、3番目のオプションは、superの呼び出し中にメソッドを実装し、オプション以外のすべてのプロパティを初期化することです。オブジェクトがストーリーボードからロードされているビューである場合は、このアプローチを取る必要があります。

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

3
2番目のオプションは、実際のケースではほとんど役に立ちません。たとえば、必要な初期化子を見てみましょうinit(collection:MPMediaItemCollection)。実際のメディアアイテムコレクションを提供する必要があります。それがこのクラスのポイントです。このクラスは、それなしではインスタンス化できません。コレクションを分析し、12個のインスタンス変数を初期化します。これが唯一の指定されたイニシャライザであることの要点です!したがって、init(coder:)ここに提供する意味のある(または意味のない)MPMediaItemCollectionはありません。fatalErrorアプローチだけが正しいです。
マット14

@matt正解です。さまざまな状況で、どちらか一方のオプションが適切に機能します。
Ben Kane 14

そうです、私は2番目のオプションを独自に発見して検討しました。時にはそれが理にかなっています。たとえば、私は自分のdiを宣言することができますinit(collection:MPMediaItemCollection!)。それはinit(coder:)nilを渡すことを可能にします。しかし、私は気づきました。いいえ、今はコンパイラをだますだけです。nilを渡すことは受け入れられないので、をスローして次にfatalError進みます。:)
マット2014

1
私はこの質問とその回答が古いものであることを知っていますが、既存の回答では対応できなかったこのエラーを実際に理解するために重要だと思ういくつかの点に対処する新しい回答を投稿しました。
nhgrif 2015

いい答えだ。このパターンを理解するには、Swiftが常にスーパー初期化子を継承するとは限らないことを理解することが不可欠であることに同意します。
ベン・ケイン

71

既存の回答から欠落しているSwift固有の情報の2つの絶対的に重要な部分があります。これは、これを完全に明確にするのに役立つと思います。

  1. プロトコルが必要なメソッドとして初期化子を指定する場合、その初期化子はSwiftのrequiredキーワードを使用してマークする必要があります。
  2. Swiftには、initメソッドに関する一連の特別な継承ルールがあります。

TL; DRはこれです:

初期化子を実装すると、スーパークラスの指定された初期化子を継承しなくなります。

継承するイニシャライザは、もしあれば、たまたまオーバーライドした指定のイニシャライザを指すスーパークラスの便利なイニシャライザです。

だから...ロングバージョンの準備はいいですか?


Swiftには、initメソッドに関する一連の特別な継承ルールがあります。

これが2つのポイントの2番目だったのはわかっていますが、最初のポイント、またはrequiredこのポイントを理解するまでキーワードが存在する理由を理解できません。この点を理解すると、もう1つはかなり明白になります。

この回答のこのセクションで説明するすべての情報は、ここにある Appleのドキュメントからのものです

アップルのドキュメントから:

Objective-Cのサブクラスとは異なり、Swiftサブクラスはデフォルトでスーパークラス初期化子を継承しません。Swiftのアプローチは、スーパークラスからの単純なイニシャライザがより特殊化されたサブクラスによって継承され、完全にまたは正しく初期化されていないサブクラスの新しいインスタンスを作成する状況を防ぎます。

鉱山を強調します。

したがって、Appleのドキュメントから直接、Swiftサブクラスが常にスーパークラスのinitメソッドを継承するとは限らない(通常はそうしない)ことがわかります。

では、いつスーパークラスから継承するのでしょうか?

サブクラスinitが親からメソッドを継承するタイミングを定義する2つのルールがあります。アップルのドキュメントから:

ルール1

サブクラスが指定された初期化子を定義していない場合、サブクラスは指定された初期化子のスーパークラスをすべて自動的に継承します。

ルール2

サブクラスがすべてのスーパークラス指定イニシャライザーの実装を提供する場合(ルール1に従って継承するか、カスタム定義をその定義の一部として提供することにより)、スーパークラスの便利な初期化子すべてを自動的に継承します。

ので、ルール2は、この会話に特に関連性がないSKSpriteNodeのは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'の引数がありません。

ここに画像の説明を入力してください

これがObjective-Cであれば、継承に問題はありません。Objective-CでBarwith を初期化した場合initWithFoo:self.barプロパティは単にになりますnil。それはおそらく素晴らしいではありませんが、それは完全だ、有効なオブジェクトが中であるために状態。それはだない。スウィフトオブジェクトが中であるために完全に有効な状態 self.barのオプションではありませんとすることはできませんnil

ここでも、イニシャライザを継承する唯一の方法は、独自のイニシャライザを提供しないことです。だから我々は、削除して継承しようとした場合Barのをinit(foo: String, bar: String)など、:

class Bar: Foo {
    var bar: String
}

これで継承に戻りますが(これはコンパイルされません)、エラーメッセージはスーパークラスinitメソッドを継承しない理由を正確に説明しています。

問題:クラス 'Bar'に初期化子がありません

Fix-It:初期化子のない保存されたプロパティ 'bar'が合成された初期化子を妨げる

サブクラスに保存されたプロパティを追加した場合、サブクラスの保存されたプロパティを認識できなかったスーパークラスの初期化子を使用して、サブクラスの有効なインスタンスを作成するSwiftの方法はありません。


さて、まあ、なぜ私は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以外に、メソッドを作成する理由が少なくとも1つあります。それがファクトリメソッドです。

適切なファクトリメソッドを記述したい場合は、戻り値の型を指定する必要があります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)
    }
}

これはエラーを生成します:

メタタイプ値を持つクラスタイプ 'Self'のオブジェクトの作成には、 '必須'イニシャライザを使用する必要があります

ここに画像の説明を入力してください

基本的には同じ問題です。Boxサブクラス化する場合、サブクラスはクラスメソッドを継承しfactoryます。だから我々は、呼び出すことができますSubclassedBox.factory()。ただし、メソッドにrequiredキーワードがないinit(size:)場合、Boxのサブクラスは、呼び出してself.init(size:)いるを継承することfactoryが保証されません。

したがってrequired、このようなファクトリメソッドが必要な場合は、そのメソッドを作成する必要があります。つまり、クラスがこのようなメソッドを実装している場合は、requiredイニシャライザメソッドがあり、ここで発生した問題とまったく同じ問題が発生します。NSCodingプロトコル。


結局のところ、Swiftのイニシャライザはわずかに異なる継承ルールのセットによって再生されるという基本的な理解にすべて結束します。つまり、スーパークラスからのイニシャライザの継承は保証されません。これは、スーパークラス初期化子が新しい格納されたプロパティを認識できず、オブジェクトを有効な状態にインスタンス化できなかったために発生します。ただし、さまざまな理由により、スーパークラスは初期化子をとしてマークする場合がありますrequired。その場合、実際にrequiredメソッドを継承する非常に具体的なシナリオの1つを使用するか、または自分で実装する必要があります。

ただし、ここで重要なのは、ここに表示されるエラーが発生した場合、クラスが実際にメソッドを実装していないということです。

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'

しかし、要点は、親クラスからメソッドを継承する2つの特別なケースのいずれかを満たさないため、のメソッドをBar継承しないことです。Fooinitinit

これがObjective-Cである場合init、Objective-Cはオブジェクトのプロパティを初期化しなくても完全に満足しているため、問題なく継承します(開発者としては、これに満足すべきではなかったはずです)。Swiftでは、これは単に行いません。無効な状態にすることはできません。スーパークラスの初期化子を継承すると、無効なオブジェクトの状態になるだけです。


この文章の意味を説明したり、例を挙げたりできますか?「(そして私たちが実装したイニシャライザを指さなかった便利なイニシャライザ)」
アビー・ジャクソン

素晴らしい答え!私はより多くのSOの投稿がなぜかについてだけではなく、なぜこのようになっていることを望みます
Alexander Vasenin 2017年

56

なぜこの問題が発生したのですか?まあ、明白な事実は、クラスが処理する準備ができていないイニシャライザに対処することが常に重要であるということです(つまり、Objective-Cでは、CocoaのプログラミングをMac OS X 10.0に戻し始めた日以来)。ドキュメントは、この点に関するあなたの責任について常に非常に明確でした。しかし、私たちの何人が完全にそして手紙にそれらを果たすために悩んだのですか?おそらく誰もいない!そしてコンパイラはそれらを強制しませんでした。それはすべて純粋に慣習的なものでした。

たとえば、この指定された初期化子を持つ私のObjective-Cビューコントローラーサブクラスでは:

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

...実際のメディアアイテムコレクションが渡されることが重要です。インスタンスがなければ、インスタンスは存在しません。しかし、init代わりに誰かが私を最低限の骨で初期化するのを防ぐための「ストッパー」は書いていない。私はそれを書いたはずです(実際には、適切に言えば、initWithNibName:bundle:継承された指定イニシャライザの実装を書いたはずです)。しかし、私は自分のクラスをそのように誤って初期化することは決してないと "知っていた"ので、面倒に迷惑をかけすぎました。これは大きな穴を残しました。Objective-Cでは、誰かベアボーン呼び出しinit、私のivarを初期化しないままにしておくことができ、パドルなしでクリークを上っています。

素晴らしく、ほとんどの場合、スウィフトは私を自分から救ってくれます。このアプリをSwiftに翻訳するとすぐに、問題全体がなくなりました。Swiftは効果的にストッパーを作成してくれます!init(collection:MPMediaItemCollection)がクラスで宣言されている唯一の指定イニシャライザである場合、bare-bonesを呼び出して初期化することはできませんinit()。それは奇跡です!

シード5で起こったinit(coder:)ことは、理論的にはこのクラスのインスタンスがnibから来る可能性があり、コンパイラーがそれを防ぐことができないため、およびnibロードinit(coder:)が呼び出されます。したがって、コンパイラーはストッパーを明示的に作成します。そして、まったく正しい。


そのような詳細な回答をありがとう。これは本当に問題に光をもたらします。
ジュリアンオソリオ2014

コンパイラーをシャットダウンする方法を教えてくれたことに対するpasta12への賛成意見ですが、そもそもそれが何をしていたのかを理解するためのあなたへの賛成意見です。
Garrett Albright

2
大きな穴かどうか、私はこのinitを呼び出すつもりはなかったので、それを含めるように強制することは完全に不審です。肥大化したコードは、誰もが必要としないオーバーヘッドです。また、両方のinitでもプロパティを初期化する必要があります。無意味!
Dan Greenfield 2014

5
@DanGreenfieldいいえ、何も初期化する必要はありません。呼び出す必要がない場合は、単にstackoverflow.com/a/25128815/341994でfatalError説明されているストッパーに入れるだけだからです。これをユーザーコードスニペットにすれば、必要な場所に配置できます。0.5秒かかります。
2014

1
@nhgrifさて、公平を期すために、質問は完全な裏話を求めていませんでした。このジャムから抜け出し、次に進む方法についてだけでした。完全な話は私の本にあります:apeth.com/swiftBook/ch04.html#_class_initializers
マット

33

追加

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

3
これは機能しますが、バグではないと思います。イニシャライザはswiftで継承されず(独自のイニシャライザが宣言されている場合)、これは必須キーワードでマークされています。唯一の問題は、クラスごとにこのメソッドのすべてのプロパティを初期化する必要があることです。これはまったく使用しないため、無駄なコードになります。または、すべてのプロパティを暗黙的にラップ解除されたオプションの型として宣言して、私も実行したくない初期化をバイパスする必要があります。
Epic Byte

1
うん!バグかもしれないと言ってすぐに、それは実際には理にかなっていることに気付きました。あなたのように私はこのinitメソッドを決して使用しないので、それは多くの無駄なコードになることに同意します。エレガントなソリューションについてはまだ
わかり

2
同じ問題がありました。「required init」で意味がありますが、swiftは私が望んでいた「簡単な」言語ではありません。これらの「オプション」はすべて、言語を必要以上に複雑にしています。また、DSLおよびAOPはサポートされていません。私はますます失望しています。
user810395 2014

2
はい、完全に同意します。多くの場合、実際にはnilにすることは許可されていないはずのプロパティをオプションとして宣言しています。一部はオプションである必要があるためオプションです(nilが有効な値であることを意味します)。そして、サブクラス化していないクラスでは、オプションを使用する必要がないため、状況は非常に複雑になり、適切なコーディングスタイルを見つけることができないようです。うまくいけば、アップルは何かを理解します。
Epic Byte

5
自分のイニシャライザを宣言しないことで、必要なイニシャライザを満足させることができ、すべてのイニシャライザが継承されることになります。
エピックバイト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.