そこで、今日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)
}
}
だから私の質問は、なぜこのエラーを受け取っているのですか?どうすれば修正できますか実装していないのは何ですか?指定された初期化子を呼び出しています。
開発者フォーラムの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)
}
2つの絶対的な重要な既存の回答から欠落しているSwift固有の情報があり、これを完全に解決するのに役立つと思います。
required
name__キーワードを使用してマークする必要があります。init
name__メソッドに関する特別な一連の継承ルールがあります。tl; drはこれです:
イニシャライザを実装すると、スーパークラスの指定されたイニシャライザを継承しなくなります。
継承するイニシャライザーがある場合、それは、オーバーライドした指定イニシャライザーを指すスーパークラスの便利なイニシャライザーのみです。
それで...長いバージョンの準備はできましたか?
init
name__メソッドに関する特別な一連の継承ルールがあります。これが2つのポイントのうち2番目だったことは知っていますが、最初のポイント、またはこのポイントを理解するまでrequired
name__キーワードが存在する理由を理解することはできません。この点を理解すると、もう1つはかなり明白になります。
この回答のこのセクションで説明する情報はすべて、Appleのドキュメント( here )からのものです。
Appleドキュメントから:
Objective-Cのサブクラスとは異なり、Swiftサブクラスはデフォルトでスーパークラス初期化子を継承しません。Swiftのアプローチは、スーパークラスの単純な初期化子は、より特殊なサブクラスに継承され、完全にまたは正しく初期化されていないサブクラスの新しいインスタンスを作成するために使用されます。
強調鉱山。
そのため、Appleのドキュメントからすぐに、Swiftサブクラスは常にスーパークラスのinit
name__メソッドを継承しないことがわかります(通常は継承しません)。
では、いつスーパークラスから継承しますか?
サブクラスがその親からinit
name__メソッドをいつ継承するかを定義する2つのルールがあります。 Appleドキュメントから:
ルール1
サブクラスが指定されたイニシャライザを定義していない場合、そのサブクラスは指定されたイニシャライザをすべて自動的に継承します。
ルール2
サブクラスが、ルール1に従って継承するか、定義の一部としてカスタム実装を提供することにより、すべてのスーパークラス指定イニシャライザーの実装を提供する場合、スーパークラスの便利なイニシャライザーをすべて自動的に継承します。
SKSpriteNode
name__のinit(coder: NSCoder)
が便利なメソッドである可能性は低いため、ルール2はこの会話には特に関係ありません。
したがって、InfoBar
name__クラスは、init(team: Team, size: CGSize)
を追加した時点までrequired
name__初期化子を継承していました。
このinit
name__メソッドを提供せず、代わりにInfoBar
name__の追加プロパティをオプションにするか、デフォルト値を提供する場合、SKSpriteNode
name__の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'の引数がありません。
これがObjective-Cであれば、継承は問題ありません。 Objective-CでBar
name__をinitWithFoo:
で初期化した場合、self.bar
プロパティは単純にnil
name__になります。おそらく素晴らしいことではありませんが、オブジェクトが完全にvalid状態になっています。not Swiftオブジェクトが完全に有効な状態。self.bar
はオプションではなく、nil
name__にはできません。
繰り返しますが、イニシャライザを継承する唯一の方法は、イニシャライザを提供しないことです。したがって、Bar
name__のinit(foo: String, bar: String)
を削除して継承しようとすると、
class Bar: Foo {
var bar: String
}
継承に戻りますが、これはコンパイルされません...エラーメッセージは、スーパークラスinit
name__メソッドを継承しない理由を正確に説明しています。
問題:クラス「Bar」には初期化子がありません
Fix-It:初期化子なしの保存されたプロパティ「bar」は合成された初期化子を防止
格納されたプロパティをサブクラスに追加した場合、サブクラスの格納されたプロパティを知ることができないスーパークラス初期化子を使用して、サブクラスの有効なインスタンスを作成するSwift方法はありません。
init(coder: NSCoder)
を実装しなければならないのですか?なぜrequired
name__ですか?Swiftのinit
name__メソッドは、継承ルールの特別なセットによって再生される場合がありますが、プロトコルの適合性は引き続きチェーンで継承されます。親クラスがプロトコルに準拠している場合、そのサブクラスはそのプロトコルに準拠する必要があります。
通常、これは問題ではありません。ほとんどのプロトコルは、Swiftの特別な継承ルールによって再生されないメソッドのみを必要とするため、プロトコルに準拠するクラスから継承している場合は、クラスがプロトコルの適合性を満たすことができるメソッドまたはプロパティ。
ただし、Swiftのinit
name__メソッドは特別なルールセットで再生され、常に継承されるわけではないことを覚えておいてください。このため、特殊なinit
name__メソッド(NSCoding
name__など)を必要とするプロトコルに準拠するクラスでは、init
name__メソッドをrequired
name__としてマークする必要があります。
この例を考えてみましょう:
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)
初期化子を必須にすることを望んでいます。クラスをfinal
name__(クラスを継承できないことを意味する)にすることで幸せにすることもできます。
それで、サブクラス化するとどうなりますか?この時点から、サブクラス化すれば大丈夫です。ただし、イニシャライザを追加すると、突然init(foo:)
を継承しなくなります。 InitProtocol
name__に準拠しなくなったため、これは問題です。プロトコルに準拠しているクラスからサブクラス化することはできず、突然そのプロトコルに準拠する必要がなくなったと突然判断します。プロトコル準拠を継承しましたが、Swiftがinit
name__メソッドの継承を処理する方法のため、そのプロトコルに準拠するために必要なものの一部を継承しておらず、実装する必要があります。
間違いなく、クラスが継承されたNSCoding
name__プロトコルに準拠していないこと、およびそれを修正するにはinit(coder: NSCoder)
を実装する必要があることを指定した場合、エラーメッセージはより明確または改善されます。確かに。
しかし、Xcodeは単にそのメッセージを生成することはできません。これは、実際には、必要なメソッドを実装または継承しないことに関する実際の問題ではないためですプロトコル準拠に加えて、init
name__メソッドをrequired
name__にする少なくとも1つの理由があります。それはファクトリーメソッドです。
適切なファクトリメソッドを作成する場合は、戻り値の型をSelf
name__(SwiftのObjective-CのinstanceType
name__と同等)に指定する必要があります。しかし、これを行うには、実際にrequired
name__初期化メソッドを使用する必要があります。
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
これによりエラーが生成されます。
クラスタイプ「Self」のオブジェクトをメタタイプ値で作成するには、「required」初期化子を使用する必要があります
基本的には同じ問題です。 Box
name__をサブクラス化すると、サブクラスはクラスメソッドfactory
name__を継承します。したがって、SubclassedBox.factory()
を呼び出すことができます。ただし、init(size:)
メソッドにrequired
name__キーワードがない場合、Box
name__のサブクラスは、factory
name__が呼び出しているself.init(size:)
を継承することは保証されません。
したがって、このようなファクトリメソッドが必要な場合は、そのメソッドをrequired
name__にする必要があります。つまり、クラスがこのようなメソッドを実装する場合は、required
name__初期化メソッドがあり、実行したものとまったく同じ問題が発生しますここにNSCoding
name__プロトコルを使用します。
最終的には、Swiftのイニシャライザーがわずかに異なる継承ルールのセットでプレイするという基本的な理解に要約されます。つまり、スーパークラスからイニシャライザーを継承する保証はありません。これは、スーパークラスのイニシャライザが新しい保存済みプロパティを認識できず、オブジェクトを有効な状態にインスタンス化できなかったために発生します。しかし、さまざまな理由で、スーパークラスは初期化子をrequired
name__としてマークする場合があります。その場合、required
name__メソッドを実際に継承する非常に具体的なシナリオのいずれかを使用するか、自分で実装する必要があります。
ただし、ここでの主なポイントは、ここで表示されるエラーが発生した場合、クラスが実際にメソッドをまったく実装していないことを意味します。
Swiftサブクラスが常に親のinit
name__メソッド(この問題を完全に理解するために絶対に重要だと思う)を常に継承するわけではないという事実を掘り下げる最後の例として、この例を検討してください。
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'
しかし、ポイントは、Bar
name__はFoo
name__のinit
name__メソッドを継承しません。親クラスからinit
name__メソッドを継承するための2つの特殊なケースのいずれも満たしていないためです。
これがObjective-Cの場合、Objective-Cはオブジェクトのプロパティを初期化せずに完全に満足しているので、問題なくinit
name__を継承します(ただし、開発者としては、これに満足すべきではありません)。 Swiftでは、これは単に実行されません。無効な状態にすることはできません。また、スーパークラスの初期化子を継承すると、無効なオブジェクトの状態になるだけです。
なぜこの問題が発生したのですか?まあ、明白な事実は、クラスが準備されていないイニシャライザを処理するために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:)
が呼び出されます。そのため、コンパイラはストッパーを明示的に記述します。そして、まったく正しい。
加える
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}