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


132

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")
    }
}

13
ドキュメンテーションでこれを読んでいて、それについても混乱していました。:/
boidkan 2015年

回答:


235

既存の答えはconvenience物語の半分しか伝えません。ストーリーの残りの半分、既存の回答のいずれもカバーしていない半分は、Desmondがコメントで投稿した質問に回答します。

Swiftがconvenienceイニシャライザから呼び出す必要があるからといって、なぜイニシャライザの前に置くよう強制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を継承していることが予想されます。しかし、そうではありません。そして、どうすればよいでしょうか?どのようにないのは、に値を代入する方法を知っているという変数を追加しますか?そうではありません。したがって、すべての値を初期化できないイニシャライザでインスタンスを初期化することはできません。Fooinit(a: Int)Fooinit(a: Int)bBarBar

これは何と関係があり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)
    }
}

convenienceここには3つのイニシャライザがあることに注意してください。つまり、継承可能な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ます。独自のインスタンス変数を追加し、デフォルト値を指定しなかったため、独自の初期化子を追加する必要があります。1つ追加しました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ます。つまり、すべてのの指定された初期化子を実装し、その初期化子を継承できます。


16
実際に質問に答え、ドキュメントに従う唯一の答え。私がOPならそれを受け入れます。
FreeNickname 2016

12
あなたは本を書くべきです;)
coolbeet

1
@SLN この回答は、Swift初期化子の継承がどのように機能するかについて多くをカバーしています。
nhgrif 2016

1
@SLNを使用してバーを作成すると、初期化されinit(a: Int)ないままになりbます。
Ian Warburton 2016年

2
@IanWarburtonこの「なぜ」に対する答えはわかりません。コメントの2番目の部分のロジックは私には聞こえますが、ドキュメントにはこれがどのように機能するかが明確に記載されており、プレイグラウンドで求めていることの例を投げかけると、動作がドキュメントに記載されているものと一致することが確認されます。
nhgrif 2016年

9

主に明快さ。あなたの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
}

便利なイニシャライザは、指定されたまたは必要なイニシャライザを呼び出す必要があります。クラスがサブクラスの場合、super.init() 初期化内で呼び出す必要があります。


2
したがって、convenienceキーワードがない場合でも、複数のイニシャライザを使用して何をしようとしているのかはコンパイラにとって完全に明白ですが、Swiftはそれについてバグを抱えています。それは私がAppleに期待していたような単純さではありません=)
Desmond Hume

2
この答えは何も答えません。あなたは「明快さ」と言いましたが、それが何かをより明確にする方法を説明しませんでした。
Robo Robok

7

まあ、最初に頭に浮かぶのは、コードの編成と読みやすさのためにクラスの継承で使用されているということです。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()と、値のないオブジェクトを作成できるため、単語convenience


2
convenience連れ去られたキーワード、スウィフトはまったく同じように動作するのに十分な情報を得ないでしょうか?
Desmond Hume 2015年

いいえ、convenienceキーワードを削除するとEmployee、引数なしではオブジェクトを初期化できません。
u54r 2015年

具体的には、を呼び出すEmployee()と、を呼び出す(継承されたconvenience)初期化子init()が呼び出されますself.init(name: "Unknown")init(name: String)も便利なイニシャライザでEmployee、指定されたイニシャライザを呼び出します。
BallpointBen

1

ここで他のユーザーが説明した点とは別に、私の理解は少しです。

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

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


1
なぜSwift convenienceからイニシャライザを呼び出す必要があるからとself.initいって、イニシャライザの前に配置する必要があるのですか?これは冗長で、ちょっと不便です。
Desmond Hume

1

Swift 2.1のドキュメントによれば、convenienceイニシャライザはいくつかの特定のルールに従う必要があります:

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

  2. convenience初期化子は、チェーン内の指定イニシャライザのどこかを呼び出す必要があります

  3. convenience初期化子を変更することはできませんANY指定イニシャライザは、一方で-それは別の初期化子と呼ばれる前にプロパティを する必要があり、別の初期化子を呼び出す前に、現在のクラスによって導入されるプロパティを初期化します。

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


おそらく、コンパイラはおそらくconvenienceキーワードなしでこれを整理できます。
nhgrif 2016

さらに、3番目のポイントは誤解を招くものです。便利なイニシャライザはプロパティのみを変更できます(プロパティを変更することはできませんlet)。プロパティを初期化することはできません。指定イニシャライザは、指定イニシャライザを呼び出す前に、導入されたすべてのプロパティを初期化する責任がありsuperます。
nhgrif

1
少なくともコンビニエンスキーワードは、開発者にそれを明確にします。重要なのは読みやすさです(さらに、開発者の期待に対してイニシャライザをチェックします)。あなたの2番目のポイントは良いものです。それに応じて私の答えを変更しました。
TheEye 2016

1

クラスは、複数の指定された初期化子を持つことができます。簡易イニシャライザは、同じクラスの指定されたイニシャライザを呼び出す必要がある二次イニシャライザです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.