既存の回答から欠落しているSwift固有の情報の2つの絶対的に重要な部分があります。これは、これを完全に明確にするのに役立つと思います。
- プロトコルが必要なメソッドとして初期化子を指定する場合、その初期化子はSwiftの
required
キーワードを使用してマークする必要があります。
- 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でBar
with を初期化した場合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
継承しないことです。Foo
init
init
これがObjective-Cである場合init
、Objective-Cはオブジェクトのプロパティを初期化しなくても完全に満足しているため、問題なく継承します(開発者としては、これに満足すべきではなかったはずです)。Swiftでは、これは単に行いません。無効な状態にすることはできません。スーパークラスの初期化子を継承すると、無効なオブジェクトの状態になるだけです。
init(collection:MPMediaItemCollection)
。実際のメディアアイテムコレクションを提供する必要があります。それがこのクラスのポイントです。このクラスは、それなしではインスタンス化できません。コレクションを分析し、12個のインスタンス変数を初期化します。これが唯一の指定されたイニシャライザであることの要点です!したがって、init(coder:)
ここに提供する意味のある(または意味のない)MPMediaItemCollectionはありません。fatalError
アプローチだけが正しいです。