クラスがスーパークラスから継承するプロパティを含めて、クラスのすべてのストアドプロパティは、初期化の間に初期値を代入する必要があります。
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 イニシャライザは、常に横にデリゲートする必要がある。
これらのルールを次の図で説明します。
このスーパークラスには、designated イニシャライザが 1 つと、convenience イニシャライザが 2 つあります。convenience イニシャライザが別の convenience イニシャライザを呼び出し、次にそのイニシャライザは designated イニシャライザを呼び出します。これは上のルール 2 と 3 を満たしています。このスーパークラスには、さらなるスーパークラスがありませんので、ルール 1 は適用されません。
この図でのサブクラスには、designated イニシャライザが 2 つと、convenience イニシャライザが 1 つあります。convenience イニシャライザは、同じクラスの別のイニシャライザを 1 つだけ呼び出すことができるため、2 つある designated イニシャライザのうち 1 つを呼び出す必要があります。これは上のルール 2 と 3 を満たしています。両 designated イニシャライザは、上のルール 1 を満たすために、スーパークラスの designated イニシャライザを呼び出す必要があります。
次の図は、クラスが 4 つある、より複雑なクラス階層を示しています。クラス初期化処理において、designated イニシャライザが階層をどのように流れるか、相互関係をシンプルに表現しています。
2 段階の初期化
Swift でのクラスの初期化は、2 段階の処理があります。フェーズ 1 は、クラスによって各ストアドプロパティに初期値が代入されます。各ストアドプロパティの初期状態が確定した後、フェーズ 2 が始まり、新しいインスタンスが利用できるようになる前に、各クラスにストアドプロパティをさらに変更する機会があります。
2 段階の初期化処理によって、クラス階層で各クラスに完全な柔軟性を与えながら、初期化を安全にしています。2 段階の初期化は、プロパティ値が初期化前にアクセスされることを防ぎ、別のイニシャライザによってプロパティ値が予期せず異なる値に設定されてしまうことを防ぎます。
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 での初期化の呼び出しを示しています。
この例では、初期化はサブクラスの convenience イニシャライザで始まっています。この convenience イニシャライザは、まだプロパティを変更することはできません。同じクラスの designated イニシャライザにデリゲートします。
designated イニシャライザは、安全チェック 1 のとおり、サブクラスのプロパティすべてに値があるようにします。そして、初期化を継続するためにスーパークラスの designated イニシャライザを呼び出します。
スーパークラスの designated イニシャライザは、スーパークラスのプロパティすべてに値があるようにします。さらなるスーパークラスは無いため、さらなるデリゲーションは必要ありません。
スーパークラスのすべてのプロパティが初期値を持つとすぐに、そのメモリは完全に初期化されたとみなされ、フェーズ 1 は完了です。
次は、フェーズ 2 での同じ初期化の呼び出しを示しています。
スーパークラスの designated イニシャライザには、この時点でインスタンスをさらに変更する機会があります(が、そうする必要はありません)。
スーパークラスの designated イニシャライザが終了すると、サブクラスの designated イニシャライザは追加の変更を行うことができます(が、そうする必要はありません)。
最後に、サブクラスのイニシャライザが終了すると、そもそも呼び出されていた convenience イニシャライザが追加の変更を行うことができます。
イニシャライザの継承とオーバーライド
Objective-C でのサブクラスと異なり、Swift のサブクラスはデフォルトではスーパークラスのイニシャライザを継承しません。スーパークラスの単純なイニシャライザが、より特殊なサブクラスによって継承されているときに、サブクラスの新しいインスタンスが不完全あるいは不正に初期化され生成されてしまう状況になることを、Swift のアプローチは防いでいます。
スーパークラスと同じイニシャライザのいくつかをサブクラスでも持たせたい場合には、サブクラス内でそれらのイニシャライザの実装を持つことができます。
スーパークラスの designated イニシャライザに一致するサブクラスのイニシャライザを記述するとき、その designated イニシャライザを実質的にオーバーライドしています。従って、サブクラスのイニシャライザ定義の前に override
を記述する必要があります。Default Initializers で説明されているように、自動的に与えられるデフォルトイニシャライザをオーバーライドする場合にも適用されます。
オーバーライドされたプロパティやメソッド、サブスクリプトでも同様で、override
があることで Swift はスーパークラスにオーバーライドに一致する 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)
イニシャライザの自動継承
上述のように、サブクラスはデフォルトではスーパークラスのイニシャライザを継承しません。しかしながら、ある条件に一致する場合には、スーパークラスのイニシャライザが自動的に継承されます。つまり実際には、よくある一般的なシナリオにおいて、イニシャライザのオーバーライドを記述する必要が無く、そうしても安全なときには、最小限の作業でスーパークラスのイニシャライザを継承することができます。
サブクラスで新たに追加するプロパティにはすべてデフォルト値を与える場合、次の 2 つのルールが適用されます。
- ルール 1
- サブクラスで designated イニシャライザを定義しない場合、スーパークラスの designated イニシャライザがすべて自動的に継承される。
- ルール 2
- サブクラスがスーパークラスの designated イニシャライザすべての実装を持つ(ルール 1 のようにすべて継承、またはすべて定義で実装する)場合には、スーパークラスのすべての convenience イニシャライザを自動的に継承する。
これらのルールは、サブクラスで convenience イニシャライザをさらに追加している場合にも適用されます。
designated イニシャライザと convenience イニシャライザの動作
以降の例では、designated イニシャライザ、convenience イニシャライザ、イニシャライザの自動継承の動作を見ていきます。この例では、3 つのクラス Food
、RecipeIngredient
、そして 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
クラスのイニシャライザチェーンを示しています。
クラスにはデフォルトメンバーワイズイニシャライザが無いため、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
クラスのイニシャライザチェーンを示しています。
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
}
}
ShoppingListItem
に purchased
の初期値を取るイニシャライザを定義していません。
プロパティすべてにデフォルト値を与えていて、かつイニシャライザを定義していないため、ShoppingListItem
は自動的にすべての designated イニシャライザと convenience イニシャライザをスーパークラスから継承します。
次の図は、3 つのクラスすべてのイニシャライザチェーン全体を示しています。
新しい 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.