クラスがスーパークラスから継承するプロパティを含めて、クラスのすべてのストアドプロパティは、初期化の間に初期値を代入する必要があります。

Swift は、すべてのストアドプロパティが初期値を受け取れるようにする、2 種類のイニシャライザをクラス型に定義しています。designated イニシャライザと convenience イニシャライザです。

designated イニシャライザと convenience イニシャライザ

designated イニシャライザは、クラスの一次的なイニシャライザです。designated イニシャライザは、そのクラスが持つすべてのプロパティを完全に初期化し、初期化プロセスをスーパークラスに継続するために、スーパークラスの適切なイニシャライザを呼び出します。

クラスには designated イニシャライザがあまり多くなく、クラスに 1 つだけということがよくあります。designated イニシャライザは、初期化処理が流れ着く先であり、初期化処理をスーパークラスに継続して流すポイントです。

すべてのクラスに少なくとも 1 つ designated イニシャライザが必要です。場合によっては、Automatic Initializer Inheritance で説明されているように、スーパークラスから 1 つ以上の designated イニシャライザを継承することで、この必要条件は満たされます。

convenience イニシャライザは、クラスの二次的で補助的なイニシャライザです。designated イニシャライザのパラメータのいくつかをデフォルト値に設定する convenience イニシャライザと同じクラスから、designated イニシャライザを呼び出す convenience イニシャライザを定義することができます。また、特定のユースケースや入力値型のクラスのインスタンスを生成する convenience イニシャライザを定義することも可能です。

designated イニシャライザと convenience イニシャライザのシンタックス

クラスの designated イニシャライザは、値型のシンプルなイニシャライザと同じように記述します。

init(parameters) {
    statements
}

convenience イニシャライザも同じスタイルで記述しますが、init キーワードの前にスペース区切りで convenience を置きます。

convenience init(parameters) {
    statements
}

クラス型のイニシャライザデリゲーション

designated イニシャライザと convenience イニシャライザとの関係を簡略化するために、Swift はイニシャライザ間のデリゲーション呼び出しに、次の 3 つのルールを適用します。

ルール 1
designated イニシャライザは、直接のスーパークラスの designated initializer を呼び出す必要がある。
ルール 2
convenience イニシャライザは、同じクラスの別のイニシャライザを呼び出す必要がある。
ルール 3
convenience イニシャライザは、最終的に designated イニシャライザを呼び出す必要がある。

これを覚えるためのシンプルな方法は:

  • designated イニシャライザは、常ににデリゲートする必要がある。
  • convenience イニシャライザは、常ににデリゲートする必要がある。

これらのルールを次の図で説明します。

image: initializerDelegation01_2x

このスーパークラスには、designated イニシャライザが 1 つと、convenience イニシャライザが 2 つあります。convenience イニシャライザが別の convenience イニシャライザを呼び出し、次にそのイニシャライザは designated イニシャライザを呼び出します。これは上のルール 2 と 3 を満たしています。このスーパークラスには、さらなるスーパークラスがありませんので、ルール 1 は適用されません。

この図でのサブクラスには、designated イニシャライザが 2 つと、convenience イニシャライザが 1 つあります。convenience イニシャライザは、同じクラスの別のイニシャライザを 1 つだけ呼び出すことができるため、2 つある designated イニシャライザのうち 1 つを呼び出す必要があります。これは上のルール 2 と 3 を満たしています。両 designated イニシャライザは、上のルール 1 を満たすために、スーパークラスの designated イニシャライザを呼び出す必要があります。

NOTE
これらのルールは、クラスのユーザが各クラスのインスタンスを生成する方法には影響がありません。上の図でのどのイニシャライザも、イニシャライザが属するクラスのインスタンスを完全に初期化して生成するために利用することができます。これらのルールは、クラスのイニシャライザの実装を記述する場合にのみ影響があります。

次の図は、クラスが 4 つある、より複雑なクラス階層を示しています。クラス初期化処理において、designated イニシャライザが階層をどのように流れるか、相互関係をシンプルに表現しています。

image: initializerDelegation02_2x

2 段階の初期化

Swift でのクラスの初期化は、2 段階の処理があります。フェーズ 1 は、クラスによって各ストアドプロパティに初期値が代入されます。各ストアドプロパティの初期状態が確定した後、フェーズ 2 が始まり、新しいインスタンスが利用できるようになる前に、各クラスにストアドプロパティをさらに変更する機会があります。

2 段階の初期化処理によって、クラス階層で各クラスに完全な柔軟性を与えながら、初期化を安全にしています。2 段階の初期化は、プロパティ値が初期化前にアクセスされることを防ぎ、別のイニシャライザによってプロパティ値が予期せず異なる値に設定されてしまうことを防ぎます。

NOTE
Swift の 2 段階の初期化処理は、Objective-C での初期化に似ています。主な違いはフェーズ 1 で、Objective-C ではすべてのプロパティにゼロまたは null 値(0 または nil)を代入します。Swift の初期化フローは、初期値を変更できるためより柔軟で、0 や nil が有効なデフォルト値ではない型を扱うことができます。

Swift のコンパイラは、2 段階の初期化がエラー無く完了していることを確認する 4 つの安全チェックを行います。

安全チェック 1
designated イニシャライザは、スーパークラスのイニシャライザにデリゲートする前に、クラスのプロパティすべてが初期化されているようにする必要がある。

上述のように、オブジェクトのメモリは、すべてのストアドプロパティの初期状態が明らかになった時点に、完全に初期化されているとみなします。このルールを満たすためには、スーパークラスに引き渡す前に、designated イニシャライザは自身のプロパティすべてが初期化されるようにする必要があります。

安全チェック 2
designated イニシャライザは、継承したプロパティに値を代入する前に、スーパークラスのイニシャライザにデリゲートする必要がある。そうしない場合には、designated イニシャライザが代入する新しい値は、自身の初期化の一部としてスーパークラスによって上書きされる。
安全チェック 3
convenience イニシャライザは、(同じクラスで定義されたプロパティを含む)プロパティに値を代入する前に、別のイニシャライザにデリゲートする必要がある。そうしない場合には、convenience イニシャライザが代入する新しい値は、自身のクラスの designated イニシャライザによって上書きされる。
安全チェック 4
フェーズ 1 のイニシャライザが完了した後までは、インスタンスメソッドを呼び出すことも、インスタンスプロパティの値を読むことも、値として self を参照することもできない。

クラスインスタンスは、フェーズ 1 が終了するまで完全には有効ではありません。フェーズ 1 が終了してクラスインスタンスが有効になると、プロパティはアクセス可能で、メソッドは呼び出し可能になります。

次は、上で見た 4 つの安全チェックをベースにした 2 段階の初期化がどのように実行されるかについてです。

フェーズ 1

  • designated イニシャライザ、または convenience イニシャライザがクラスで呼び出される。
  • そのクラスの新しいインスタンスにメモリが割り当てられる。メモリはまだ初期化されていない。
  • クラスの designated イニシャライザが、そのクラスのストアドプロパティすべてに値があることを確認する。これらのストアドプロパティのメモリが初期化される。
  • designated イニシャライザがスーパークラスのイニシャライザに引き渡し、そのストアドプロパティに同じタスクを行う。
  • クラス継承チェーンのトップに到達するまでこれを継続する。
  • チェーンのトップに到達し、final クラスのストアドプロパティすべてに値があるようにすると、インスタンスのメモリは完全に初期化されたとみなされ、フェーズ 1 が完了する。

フェーズ 2

  • チェーンのトップから戻って下りてくるときに、チェーンの各 designated イニシャライザでインスタンスを変更することができる。このときには、イニシャライザは self にアクセスでき、プロパティを変更でき、インスタンスメソッドを変更できる。
  • 最後に、チェーンにある convenient イニシャライザは、インスタンスを変更し、self を扱うことができる。

次は、仮説上のサブクラスとスーパークラスで、フェーズ 1 での初期化の呼び出しを示しています。

image: twoPhaseInitialization01_2x

この例では、初期化はサブクラスの convenience イニシャライザで始まっています。この convenience イニシャライザは、まだプロパティを変更することはできません。同じクラスの designated イニシャライザにデリゲートします。

designated イニシャライザは、安全チェック 1 のとおり、サブクラスのプロパティすべてに値があるようにします。そして、初期化を継続するためにスーパークラスの designated イニシャライザを呼び出します。

スーパークラスの designated イニシャライザは、スーパークラスのプロパティすべてに値があるようにします。さらなるスーパークラスは無いため、さらなるデリゲーションは必要ありません。

スーパークラスのすべてのプロパティが初期値を持つとすぐに、そのメモリは完全に初期化されたとみなされ、フェーズ 1 は完了です。

次は、フェーズ 2 での同じ初期化の呼び出しを示しています。

image: twoPhaseInitialization02_2x

スーパークラスの designated イニシャライザには、この時点でインスタンスをさらに変更する機会があります(が、そうする必要はありません)。

スーパークラスの designated イニシャライザが終了すると、サブクラスの designated イニシャライザは追加の変更を行うことができます(が、そうする必要はありません)。

最後に、サブクラスのイニシャライザが終了すると、そもそも呼び出されていた convenience イニシャライザが追加の変更を行うことができます。

イニシャライザの継承とオーバーライド

Objective-C でのサブクラスと異なり、Swift のサブクラスはデフォルトではスーパークラスのイニシャライザを継承しません。スーパークラスの単純なイニシャライザが、より特殊なサブクラスによって継承されているときに、サブクラスの新しいインスタンスが不完全あるいは不正に初期化され生成されてしまう状況になることを、Swift のアプローチは防いでいます。

NOTE
スーパークラスのイニシャライザはある状況では継承されますが、そうすることが安全で適切な場合に限られます。詳細は Automatic Initializer Inheritance を確認してください。

スーパークラスと同じイニシャライザのいくつかをサブクラスでも持たせたい場合には、サブクラス内でそれらのイニシャライザの実装を持つことができます。

スーパークラスの designated イニシャライザに一致するサブクラスのイニシャライザを記述するとき、その designated イニシャライザを実質的にオーバーライドしています。従って、サブクラスのイニシャライザ定義の前に override を記述する必要があります。Default Initializers で説明されているように、自動的に与えられるデフォルトイニシャライザをオーバーライドする場合にも適用されます。

オーバーライドされたプロパティやメソッド、サブスクリプトでも同様で、override があることで Swift はスーパークラスにオーバーライドに一致する designated イニシャライザがあることをチェックし、オーバーライドするイニシャライザのパラメータが意図したとおりに指定されていることを確認します。

NOTE
イニシャライザのサブクラスでの実装が convenience イニシャライザの場合でも、スーパークラスの designated イニシャライザをオーバーライドするときは常に override を記述します。

一方で、スーパークラスの convenience イニシャライザに一致するサブクラスのイニシャライザを記述する場合でも、Initializer Delegation for Class Typesで説明されているルールにあるように、スーパークラスの convenience イニシャライザがサブクラスから直接呼び出されることはありません。従って、(厳密に言えば)スーパークラスのイニシャライザのオーバーライドをサブクラスは提供しません。結果として、スーパークラスの convenience イニシャライザに一致する実装のときでも override を記述しません。

次の例では、ベースクラス Vehicle を定義しています。このベースクラスは、ストアドプロパティ numberOfWheels をデフォルトの Int 値 0 で宣言しています。numberOfWheels プロパティは、乗り物の特徴を表す説明を String で生成するコンピューテッドプロパティ description で使用されています。

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle クラスではストアドプロパティにデフォルト値があり、イニシャライザ自体は定義していません。結果として、Default Initializers で説明されているように、デフォルトイニシャライザが自動的にできます。デフォルトイニシャライザ(が利用できるとき)は常に designated イニシャライザで、numberOfWheels が 0 の新しい Vehicle インスタンスを生成するために利用できます。

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

次の例では、Vehicle のサブクラス Bicycle を定義しています。

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

サブクラス Bicycle は designated イニシャライザ init() を定義しています。この designated イニシャライザは、Bicycle のスーパークラスの designated イニシャライザに一致しているため、Bicycle のイニシャライザには override が付けられています。

Bicycle の init() イニシャライザは、Bicycle クラスのスーパークラスのデフォルトイニシャライザを呼び出す super.init() の呼び出しで始まっています。これによって、継承したプロパティ numberOfWheels を Bicycle が変更できるようになる前に、プロパティが Vehicle によって初期化されるようにしています。super.init() を呼び出した後、numberOfWheels の値は新しい値 2 で置き換えられます。

Bicycle インスタンスを生成した場合、numberOfWheels プロパティが更新されていることを確認するために、継承したコンピューテッドプロパティ description を呼び出すことができます。

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
NOTE
サブクラスでは初期化の間に、継承した変数プロパティを変更することはできますが、継承した定数プロパティを変更することはできません。

イニシャライザの自動継承

上述のように、サブクラスはデフォルトではスーパークラスのイニシャライザを継承しません。しかしながら、ある条件に一致する場合には、スーパークラスのイニシャライザが自動的に継承されます。つまり実際には、よくある一般的なシナリオにおいて、イニシャライザのオーバーライドを記述する必要が無く、そうしても安全なときには、最小限の作業でスーパークラスのイニシャライザを継承することができます。

サブクラスで新たに追加するプロパティにはすべてデフォルト値を与える場合、次の 2 つのルールが適用されます。

ルール 1
サブクラスで designated イニシャライザを定義しない場合、スーパークラスの designated イニシャライザがすべて自動的に継承される。
ルール 2
サブクラスがスーパークラスの designated イニシャライザすべての実装を持つ(ルール 1 のようにすべて継承、またはすべて定義で実装する)場合には、スーパークラスのすべての convenience イニシャライザを自動的に継承する。

これらのルールは、サブクラスで convenience イニシャライザをさらに追加している場合にも適用されます。

NOTE
ルール 2 の一環として、サブクラスはスーパークラスの designated イニシャライザを、サブクラスの convenience イニシャライザとして実装することができます。

designated イニシャライザと convenience イニシャライザの動作

以降の例では、designated イニシャライザ、convenience イニシャライザ、イニシャライザの自動継承の動作を見ていきます。この例では、3 つのクラス FoodRecipeIngredient、そして ShoppingListItem の階層を定義し、それぞれのイニシャライザがどのように作用するかを説明しています。

階層のベースクラスは Food で、食材の名前を持つシンプルなクラスです。Food クラスには Stringプロパティ name と、Food インスタンスを生成するイニシャライザが 2 つあります。

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

次の図は、Food クラスのイニシャライザチェーンを示しています。

image: initializersExample01_2x

クラスにはデフォルトメンバーワイズイニシャライザが無いため、Food クラスは引数に name を取る designated イニシャライザを持っています。このイニシャライザは、具体的な名前で新しい Food インスタンスを生成するために利用できます。

let namedMeat = Food(name: "Bacon")
// namedMeat の name は "Bacon"

新しい Food インスタンスのすべてのストアドプロパティが完全に初期化されるため、Food クラスの init(name: String) イニシャライザは designated イニシャライザです。Food クラスにはスーパークラスが無いため、初期化を完了するために init(name: String) イニシャライザで super.init() を呼び出す必要はありません。

また、Food クラスには引数が無い convenience イニシャライザ init() があります。init() イニシャライザは、新しい食材の name 値にデフォルトのプレースホルダ名 [Unnamed] を与えて、Foodクラスの init(name: String) にデリゲートしています。

let mysteryMeat = Food()
// mysteryMeat の name は "[Unnamed]"

階層で 2 番目のクラスは、Food のサブクラス RecipeIngredient です。RecipeIngredient クラスは料理レシピの材料をモデル化しています。(Food から継承した name プロパティに加えて)Int プロパティの quantity を持ち、RecipeIngredient インスタンスを生成する 2 つのイニシャライザを定義しています。

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

次の図は、RecipeIngredient クラスのイニシャライザチェーンを示しています。

image: initializersExample02_2x

RecipeIngredient クラスには、新しい RecipeIngredient インスタンスのプロパティすべてにデータを入れるために使用できる、designated イニシャライザ init(name: String, quantity: Int) があります。このイニシャライザは、RecipeIngredient の新しいプロパティである quantity プロパティに、渡された quantity 引数を代入して始まっています。その後、イニシャライザは Food クラスの init(name: String) イニシャライザにデリゲートします。この処理は、Two-Phase Initialization の安全チェック 1 を満たします。

また、RecipeIngredient は名前だけで RecipeIngredient インスタンスを生成するために convenience イニシャライザ init(name: String)を定義しています。この convenience イニシャライザは、明示的な数量なく生成された ReideIngredient インスタンスの数量を 1 と仮定しています。この convenience イニシャライザの定義が、より速く便利に RecipeIngredient インスタンスを生成するできるようにし、数量が 1 つの RecipeIngredient インスタンスをいくつか生成するときのコードの重複を避けています。この convenience イニシャライザは、クラスの designated イニシャライザに quantity 値の 1 を渡して単にデリゲートします。

RecipeIngredient の convenience イニシャライザ init(name: String) は、Food の designated イニシャライザ init(name: String) と同じパラメータを取ります。この convenience イニシャライザがスーパークラスの designated イニシャライザをオーバーライドするため、(Initializer Inheritance and Overriding で説明されているように)override を付ける必要があります。

RecipeIngredient には convenience イニシャライザとして init(name: String) イニシャライザがありますが、それにもかかわらず RecipeIngredient はスーパークラスの designated イニシャライザをすべて実装しています。従って、RecipeIngredient はスーパークラスの convenience イニシャライザもすべて自動的に継承します。

この例では、RecipeIngredient のスーパークラスは Food で、convenience イニシャライザ init()が 1 つあります。従って、このイニシャライザは RecipeIngredient によって継承されます。Foodでなく RecipeIngredient の init(name: String) にデリゲートすることを除き、継承した init() 関数は、Food のものとまったく同じようになります。

これら 3 つのイニシャライザはすべて、新しい RecipeIngredient インスタンスを生成するために使用することができます。

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

階層で 3 番目の最後のクラスは、RecipeIngredient のサブクラス ShoppingListItem です。ShoppingListList クラスは買い物リストにあるレシピ材料をモデル化しています。

買い物リストにあるすべてのアイテムは「未購入」で始まります。これを表すために、ShoppingListItem にブールのプロパティ purchased をデフォルト値 false で持たせています。また、ShoppingListItem インスタンスの説明テキストを返すコンピューテッドプロパティ description を ShoppingListItem に追加しています。

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}
NOTE
(ここでモデル化された)買い物リストのアイテムは常に未購入で始まるため、ShoppingListItem に purchased の初期値を取るイニシャライザを定義していません。

プロパティすべてにデフォルト値を与えていて、かつイニシャライザを定義していないため、ShoppingListItem は自動的にすべての designated イニシャライザと convenience イニシャライザをスーパークラスから継承します。

次の図は、3 つのクラスすべてのイニシャライザチェーン全体を示しています。

image: initializersExample03_2x

新しい ShoppingListItem を生成するために、継承した 3 つのイニシャライザすべてを使用することができます。

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

ここでは、3 つの新しい ShoppingListItem インスタンスを含む配列リテラルから、新しい配列 breakfastList が生成されています。配列の型は、[ShoppingListItem] と推論されます。配列が生成された後、配列の最初の ShoppingListItem の名前が "[Unnamed]" から "Orange juice" に変更され、購入済みに変更されています。配列にある各アイテムの説明を出力して、期待どおりにデフォルトの状態が設定されていることを表示しています。


Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.