web-dev-qa-db-ja.com

Swiftで便利なキーワードが必要なのはなぜですか?

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")
    }
}
120
Desmond Hume

既存の回答は、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に設定してコンパイルします。どの言語から来ているかに応じて、BarFooの初期化子init(a: Int)を継承していることを期待するかもしれません。しかし、そうではありません。そして、どうすればそれができますか? Fooinit(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つの異なる方法でインスタンス化できます。

enter image description here

それでは、サブクラスを作成しましょう。

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初期化子を継承していません。

enter image description here

では、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初期化子をすべて継承します。

enter image description here

一致するシグニチャを持つイニシャライザがconvenienceとしてマークされているという事実は、ここでは違いはありません。これは、Inheritorに指定された初期化子が1つだけであることを意味します。したがって、Inheritorから継承する場合は、指定されたイニシャライザーを1つ実装するだけで、Inheritorの簡易イニシャライザーを継承します。つまり、Baseの指定された初期化子であり、convenience初期化子を継承できます。

214
nhgrif

主に明快。 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()を呼び出す必要があります。

9
Nate Mann

さて、最初に思い浮かぶのは、コードの整理と読みやすさのためにクラス継承で使用されることです。 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

7
u54r

Swift 2.1ドキュメント によると、convenience初期化子はいくつかの特定のルールに従う必要があります。

  1. convenienceイニシャライザーは、スーパークラスではなく、同じクラスの初期化子のみを呼び出すことができます(アクロスのみ、アップではありません)

  2. convenience初期化子は、チェーンのどこかで指定された初期化子を呼び出す必要があります

  3. convenience初期化子は、別の初期化子を呼び出す前にANYプロパティを変更できません-指定された初期化子はhas to前に現在のクラスによって導入されたプロパティを初期化します別の初期化子を呼び出します。

convenienceキーワードを使用することにより、Swiftコンパイラーは、これらの条件をチェックする必要があることを認識します。そうでない場合はできません。

1
TheEye

他のユーザーがここで説明した点とは別に、私のちょっとした理解があります。

便利なイニシャライザと拡張機能の関係を強く感じています。私にとって便利なイニシャライザは、既存のクラスの初期化を変更する(ほとんどの場合、短くするか簡単にする)ときに最も便利です。

たとえば、使用するサードパーティクラスには、4つのパラメータを持つinitがありますが、アプリケーションでは最後の2つのクラスの値は同じです。より多くの入力を避け、コードをきれいにするために、2つのパラメーターのみでconvenience initを定義し、その内部でデフォルト値のlast toパラメーターでself.initを呼び出します。

1
Abdullah

クラスには、複数の初期化子を指定できます。簡易イニシャライザは、同じクラスの指定されたイニシャライザを呼び出す必要があるセカンダリイニシャライザです。

1
Abdul Yasin