web-dev-qa-db-ja.com

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

そこで、今日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)

    }

}

だから私の質問は、なぜこのエラーを受け取っているのですか?どうすれば修正できますか実装していないのは何ですか?指定された初期化子を呼び出しています。

156
Epic Byte

開発者フォーラムのApple従業員から:

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

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

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


どちらかといえばうまくいく別のオプションは、次のようにメソッドを便利なinitとして実装することです。

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

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


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

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

    super.init(coder: aDecoder)
}
128
Ben Kane

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

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

tl; drはこれです:

イニシャライザを実装すると、スーパークラスの指定されたイニシャライザを継承しなくなります。

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

それで...長いバージョンの準備はできましたか?


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

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

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

Appleドキュメントから:

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

強調鉱山。

そのため、Appleのドキュメントからすぐに、Swiftサブクラスは常にスーパークラスのinitname__メソッドを継承しないことがわかります(通常は継承しません)。

では、いつスーパークラスから継承しますか?

サブクラスがその親からinitname__メソッドをいつ継承するかを定義する2つのルールがあります。 Appleドキュメントから:

ルール1

サブクラスが指定されたイニシャライザを定義していない場合、そのサブクラスは指定されたイニシャライザをすべて自動的に継承します。

ルール2

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

SKSpriteNodename__のinit(coder: NSCoder)が便利なメソッドである可能性は低いため、ルール2はこの会話には特に関係ありません。

したがって、InfoBarname__クラスは、init(team: Team, size: CGSize)を追加した時点までrequiredname__初期化子を継承していました。

このinitname__メソッドを提供せず、代わりにInfoBarname__の追加プロパティをオプションにするか、デフォルト値を提供する場合、SKSpriteNodename__のinit(coder: NSCoder)を継承していました。ただし、独自のカスタム初期化子を追加すると、スーパークラスの指定された初期化子(および実装した初期化子を指していない convenience initializers )の継承を停止しました。

したがって、単純化した例として、私はこれを提示します:

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

これがObjective-Cであれば、継承は問題ありません。 Objective-CでBarname__をinitWithFoo:で初期化した場合、self.barプロパティは単純にnilname__になります。おそらく素晴らしいことではありませんが、オブジェクトが完全にvalid状態になっています。not Swiftオブジェクトが完全に有効な状態。self.barはオプションではなく、nilname__にはできません。

繰り返しますが、イニシャライザを継承する唯一の方法は、イニシャライザを提供しないことです。したがって、Barname__のinit(foo: String, bar: String)を削除して継承しようとすると、

class Bar: Foo {
    var bar: String
}

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

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

Fix-It:初期化子なしの保存されたプロパティ「bar」は合成された初期化子を防止

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


さて、まあ、なぜinit(coder: NSCoder)を実装しなければならないのですか?なぜrequiredname__ですか?

Swiftのinitname__メソッドは、継承ルールの特別なセットによって再生される場合がありますが、プロトコルの適合性は引き続きチェーンで継承されます。親クラスがプロトコルに準拠している場合、そのサブクラスはそのプロトコルに準拠する必要があります。

通常、これは問題ではありません。ほとんどのプロトコルは、Swiftの特別な継承ルールによって再生されないメソッドのみを必要とするため、プロトコルに準拠するクラスから継承している場合は、クラスがプロトコルの適合性を満たすことができるメソッドまたはプロパティ。

ただし、Swiftのinitname__メソッドは特別なルールセットで再生され、常に継承されるわけではないことを覚えておいてください。このため、特殊なinitname__メソッド(NSCodingname__など)を必要とするプロトコルに準拠するクラスでは、initname__メソッドをrequiredname__としてマークする必要があります。

この例を考えてみましょう:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

これはコンパイルされません。次の警告が生成されます。

問題:初期化子の要件「init(foo :)」は、非最終クラス「ConformingClass」の「required」初期化子によってのみ満たすことができます

Fix-It:挿入が必要

init(foo: Int)初期化子を必須にすることを望んでいます。クラスをfinalname__(クラスを継承できないことを意味する)にすることで幸せにすることもできます。

それで、サブクラス化するとどうなりますか?この時点から、サブクラス化すれば大丈夫です。ただし、イニシャライザを追加すると、突然init(foo:)を継承しなくなります。 InitProtocolname__に準拠しなくなったため、これは問題です。プロトコルに準拠しているクラスからサブクラス化することはできず、突然そのプロトコルに準拠する必要がなくなったと突然判断します。プロトコル準拠を継承しましたが、Swiftがinitname__メソッドの継承を処理する方法のため、そのプロトコルに準拠するために必要なものの一部を継承しておらず、実装する必要があります。


さて、これはすべて理にかなっています。しかし、なぜもっと役立つエラーメッセージを取得できないのですか?

間違いなく、クラスが継承されたNSCodingname__プロトコルに準拠していないこと、およびそれを修正するにはinit(coder: NSCoder)を実装する必要があることを指定した場合、エラーメッセージはより明確または改善されます。確かに。

しかし、Xcodeは単にそのメッセージを生成することはできません。これは、実際には、必要なメソッドを実装または継承しないことに関する実際の問題ではないためですプロトコル準拠に加えて、initname__メソッドをrequiredname__にする少なくとも1つの理由があります。それはファクトリーメソッドです。

適切なファクトリメソッドを作成する場合は、戻り値の型をSelfname__(SwiftのObjective-CのinstanceTypename__と同等)に指定する必要があります。しかし、これを行うには、実際にrequiredname__初期化メソッドを使用する必要があります。

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

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

したがって、このようなファクトリメソッドが必要な場合は、そのメソッドをrequiredname__にする必要があります。つまり、クラスがこのようなメソッドを実装する場合は、requiredname__初期化メソッドがあり、実行したものとまったく同じ問題が発生しますここにNSCodingname__プロトコルを使用します。


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

ただし、ここでの主なポイントは、ここで表示されるエラーが発生した場合、クラスが実際にメソッドをまったく実装していないことを意味します。

Swiftサブクラスが常に親のinitname__メソッド(この問題を完全に理解するために絶対に重要だと思う)を常に継承するわけではないという事実を掘り下げる最後の例として、この例を検討してください。

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'

しかし、ポイントは、Barname__はFooname__のinitname__メソッドを継承しません。親クラスからinitname__メソッドを継承するための2つの特殊なケースのいずれも満たしていないためです。

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

69
nhgrif

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

たとえば、この指定された初期化子を持つ私のObjective-C View Controllerサブクラスでは:

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

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

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

シード5で発生したのは、コンパイラがinit(coder:)の場合に奇跡が機能しないことをコンパイラが認識したということです。ペン先がロードされ、init(coder:)が呼び出されます。そのため、コンパイラはストッパーを明示的に記述します。そして、まったく正しい。

56
matt

加える

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