Swift はメソッドと初期化子のオーバーロードをサポートしているため、複数のメソッドを並べてinit
、都合の良い方を使用することができます。
class Person {
var name:String
init(name: String) {
self.name = name
}
init() {
self.name = "John"
}
}
では、convenience
キーワードはなぜ存在するのでしょうか? 次のキーワードが著しく優れているのはなぜでしょうか?
class Person {
var name:String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "John")
}
}
ベストアンサー1
既存の回答は、convenience
物語の半分しか語っていません。物語の残りの半分、つまり既存の回答のどれもカバーしていない半分は、Desmond がコメントに投稿した質問に対する答えです。
convenience
Swift では、初期化子から呼び出す必要があるという理由だけで、初期化子の前に置くことを強制するのはなぜでしょうかself.init
?
私は少し触れましたがこの答え、その中で私はいくつかのSwift の初期化ルールの詳細について説明しましたが、そこでの主な焦点は単語にありましたrequired
。しかし、その回答は、この質問とこの回答に関連する内容にまだ触れています。Swift の初期化継承がどのように機能するかを理解する必要があります。
Swift では初期化されていない変数が許可されていないため、継承元のクラスからすべての (またはいずれかの) 初期化子を継承できる保証はありません。サブクラスを作成し、そのサブクラスに初期化されていないインスタンス変数を追加すると、初期化子の継承が停止します。独自の初期化子を追加するまで、コンパイラーから警告が送られます。
明確に言えば、初期化されていないインスタンス変数とは、デフォルト値が与えられていないインスタンス変数のことです (オプションと暗黙的にアンラップされたオプションは、自動的にデフォルト値 を想定していることに留意してくださいnil
)。
この場合は次のようになります。
class Foo {
var a: Int
}
a
初期化されていないインスタンス変数です。a
デフォルト値を指定しないとコンパイルされません。
class Foo {
var a: Int = 0
}
またはa
初期化メソッドで初期化します。
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
さて、 をサブクラス化すると何が起こるか見てみFoo
ましょう。
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
そうですか? 変数を追加し、b
コンパイルできるように値を設定するための初期化子を追加しました。 使用言語によっては、が の初期化子Bar
を継承していると思われるかもしれません。 しかし、そうではありません。 どうしてそれができるのでしょう? は、追加された変数に値を割り当てる方法をどうやって知るのでしょうか? 知りません。 したがって、すべての値を初期化できない初期化子を使用してインスタンスを初期化することはできません。Foo
init(a: Int)
Foo
init(a: Int)
b
Bar
Bar
これらは と何の関係があるのでしょうかconvenience
?
さて、見てみましょう初期化子の継承に関するルール:
ルール1
サブクラスで指定初期化子を定義していない場合は、スーパークラスの指定初期化子をすべて自動的に継承します。
ルール2
サブクラスが、ルール 1 に従って継承するか、定義の一部としてカスタム実装を提供することによって、スーパークラスの指定されたイニシャライザの実装をすべて提供すると、スーパークラスの便利なイニシャライザのすべてが自動的に継承されます。
便利な初期化子について言及しているルール 2 に注意してください。
convenience
キーワードは何でしょうする初期化子を指定するだけです継承される可能性があるデフォルト値のないインスタンス変数を追加するサブクラスによって。
次の例のクラスを見てみましょうBase
:
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
ここで3 つの初期化子があることに注意してくださいconvenience
。つまり、継承可能な初期化子が 3 つあるということです。また、指定初期化子が 1 つあります (指定初期化子とは、単に便利な初期化子ではない初期化子のことです)。
基本クラスのインスタンスは、次の 4 つの方法でインスタンス化できます。
それでは、サブクラスを作成しましょう。
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
を継承していますBase
。独自のインスタンス変数を追加しましたが、デフォルト値を与えていないため、独自の初期化子を追加する必要があります。 を追加しましたinit(a: Int, b: Int, c: Int)
が、クラスの指定初期化子のシグネチャと一致しませんBase
: init(a: Int, b: Int)
。つまり、継承していないということです。どれでも初期化子Base
:
Base
では、 から継承したが、 の指定初期化子と一致する初期化子を実装した場合はどうなるでしょうかBase
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
ここで、このクラスに直接実装した 2 つの初期化子に加えて、クラスの指定初期化子と一致する初期化子を実装したため、クラスのBase
すべての初期化子を継承できるようになります。Base
convenience
一致するシグネチャを持つ初期化子が としてマークされているという事実は、convenience
ここでは何の違いもありません。Inheritor
には指定初期化子が 1 つだけあることを意味するだけです。 したがって、 を継承する場合はInheritor
、その 1 つの指定初期化子を実装するだけで済み、その後 のInheritor
便利な初期化子を継承します。つまり、 のBase
指定初期化子をすべて実装し、 の初期化子を継承できることになりますconvenience
。