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")
}
}
既存の回答は、convenience
ストーリーの半分のみを伝えます。ストーリーの残りの半分、既存の回答のいずれもカバーしていない半分は、Desmondがコメントで投稿した質問に答えます。
Swiftが
self.init
を呼び出す必要があるからといって、イニシャライザーの前にconvenience
を配置するように強制するのはなぜですか。`
この回答 で少し触れましたが、Swiftの初期化ルールの詳細をいくつかでカバーしていますが、主な焦点はrequired
Wordにありました。しかし、その答えはまだこの質問とこの答えに関連する何かを扱っていました。 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
キーワードdoes doは、デフォルト値なしでインスタンス変数を追加するサブクラスによって、どの初期化子継承可能を示すかを示しています。
この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
からany初期化子を継承していません。
では、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
初期化子を継承できます。
主に明快。 2番目の例から、
init(name: String) {
self.name = name
}
または指定が必要です。すべての定数と変数を初期化する必要があります。便利な初期化子はオプションであり、通常は初期化を簡単にするために使用できます。たとえば、Personクラスにオプションの変数genderがあるとします。
var gender: Gender?
ここで、性別は列挙型です
enum Gender {
case Male, Female
}
あなたはこのような便利な初期化子を持つことができます
convenience init(maleWithName: String) {
self.init(name: name)
gender = .Male
}
convenience init(femaleWithName: String) {
self.init(name: name)
gender = .Female
}
便利な初期化子はdesignatedまたは必要な初期化子を呼び出す必要があります。クラスがサブクラスである場合、その初期化内でsuper.init()
を呼び出す必要があります。
さて、最初に思い浮かぶのは、コードの整理と読みやすさのためにクラス継承で使用されることです。 Person
クラスを続けて、このようなシナリオを考えてください
class Person{
var name: String
init(name: String){
self.name = name
}
convenience init(){
self.init(name: "Unknown")
}
}
class Employee: Person{
var salary: Double
init(name:String, salary:Double){
self.salary = salary
super.init(name: name)
}
override convenience init(name: String) {
self.init(name:name, salary: 0)
}
}
let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}
便利なイニシャライザを使用すると、値のないEmployee()
オブジェクトを作成できるため、Word convenience
Swift 2.1ドキュメント によると、convenience
初期化子はいくつかの特定のルールに従う必要があります。
convenience
イニシャライザーは、スーパークラスではなく、同じクラスの初期化子のみを呼び出すことができます(アクロスのみ、アップではありません)
convenience
初期化子は、チェーンのどこかで指定された初期化子を呼び出す必要があります
convenience
初期化子は、別の初期化子を呼び出す前にANYプロパティを変更できません-指定された初期化子はhas to前に現在のクラスによって導入されたプロパティを初期化します別の初期化子を呼び出します。
convenience
キーワードを使用することにより、Swiftコンパイラーは、これらの条件をチェックする必要があることを認識します。そうでない場合はできません。
他のユーザーがここで説明した点とは別に、私のちょっとした理解があります。
便利なイニシャライザと拡張機能の関係を強く感じています。私にとって便利なイニシャライザは、既存のクラスの初期化を変更する(ほとんどの場合、短くするか簡単にする)ときに最も便利です。
たとえば、使用するサードパーティクラスには、4つのパラメータを持つinit
がありますが、アプリケーションでは最後の2つのクラスの値は同じです。より多くの入力を避け、コードをきれいにするために、2つのパラメーターのみでconvenience init
を定義し、その内部でデフォルト値のlast toパラメーターでself.init
を呼び出します。
クラスには、複数の初期化子を指定できます。簡易イニシャライザは、同じクラスの指定されたイニシャライザを呼び出す必要があるセカンダリイニシャライザです。