はじめに
SwiftにはClass, Structure, Enumerationと様々なものがありますが、その全てにイニシャライザというものがあります。
本記事ではこのイニシャライザに関する事柄について説明します。
イニシャライザの種類
- Failable Initializer
- Default Initializer
Classに限り以下のイニシャライザがあります。
- Designated Initializer
- Convenience Initializer
- Required Initializer
また、Structureに限り以下のイニシャライザがあります。
- Memberwise Initializer
Failable Initializer
初期化に失敗することができるイニシャライザで以下のように書きます
class Sample {
// Optional<Sample>を返す
init?() {
// ...
}
// ImplicitlyUnwrappedOptional<Sample>を返す
init!() {
// ...
}
Swiftのイニシャライザではreturn
を書く必要がありませんがFailable Initializerの場合は失敗したと判断した時点でreturn nil
を実行させる必要があります。
init?(name: String) {
if name.isEmpty {
return nil
}
}
Designated Initializer
Designated Initializerは例えば以下のように書きます。
init() {
// ...
}
init(name: String) {
self.name = name
super.init()
}
Designated Initializerは後述するDefault Initializer, Memberwise Initializerが暗黙的に定義されていない場合かならず1つは実装する必要があります。
例えば以下の様なコードはコンパイルエラーとなります。
class Sample {
let name: String
}
また、Designated Initializerを追加すると、それに対して暗黙的に定義されていたDefault InitializerやMemberwise Initializerは利用できなくなります。
さらにDesignated Initializerはあるクラスを継承している場合、親クラスのDesignated Initializerを必ず呼ばなくてはなりません。
Convenience Initializer
Convenience Initializer は以下のようにconvenience
修飾子を付けて書きます。
convenience init() {
/// ...
}
Convenience Initializerは同じクラス(親クラスのものでもない)のDesignated Initializerを呼び出す(移譲する)必要があり、Designated Initializerに対して補助的な役割を担います。
例えば以下のようにして実装します。
class Sample {
let name: String
init(name: String) {
self.name = name
}
// nameを指定しなくても初期化できる補助的なイニシャライザ
convenience init() {
self.init(name: "Guest")
}
}
Required Initializer
サブクラスに必ず実装させたいイニシャライザです。
以下のようにして書きます。
required init(name: String)
required修飾子をつけたイニシャライザをオーバーライドする際はoverride修飾子は不要です。
例えば以下のようにして実装します。
class Sample {
let name: String
required init(name: String) {
self.name = name
}
}
class Example: Sample {
required init(name :String) {
super.init(name: name)
}
}
Default Initializer
イニシャライザでプロパティに値をセットする必要が無い場合に、Designated Initializerが実装されていない場合は以下のようにDefault Initializerが暗黙的に追加されます。
class Sample {
// "Guest"が宣言時に代入されているためイニシャライザで代入する必要はない(むしろ定数であるためできない)
let name: String = "Guest"
}
struct Example {
// Optionalなのでイニシャライザで何も指定しなくともnilになる
var name: String?
}
// Default Initializer を使って初期化
let sample = Sample()
let example = Example()
Designated Initializerを追加するとDefault Initializerは呼び出せなくなります。
Memberwise Initializer
Structureに対してイニシャライザが実装されていない時に、イニシャライザで値の代入が必要なプロパティがある場合にそれらを引数にとったイニシャライザが暗黙的に追加されます。
これをMemberwise Initializerといいます。
struct Sample {
let name: String = "Guest"
let number: Int
}
let sample = Sample(number: 1)
Initializerを追加するとMemberwise Initializerは呼び出せなくなります。
イニシャライザの継承について
Swiftにおいてデフォルトでは親クラスのイニシャライザは継承しません。
しかし、以下の条件に当てはまる場合は自動的に対応するイニシャライザが継承されます。
Case 1 Designated Initializerが実装されていない場合はDesignated Initializer, Convenience Initializerがすべて継承される
Case 2 親クラスのすべてのDesignated Initializerをoverrideしている場合はすべてのConvenience Initializerが継承される
//Case 1
class Sample {
let title: String
let body: String
init(title: String?, body: String) {
self.title = title ?? ""
self.body = body
}
convenience init() {
self.init(title: nil, body: "No description")
}
}
class Example: Sample {
}
_ = Example(title: "Title", body: "Body")
_ = Example()
//Case 2
class Sample {
let title: String
let body: String
init(title: String?, body: String) {
self.title = title ?? ""
self.body = body
}
convenience init(body: String) {
self.init(title: nil, body: body)
}
}
class Example: Sample {
override init(title: String?, body: String) {
super.init(title: title, body: body)
}
}
_ = Example(body: "No description")
Classの初期化の順序について
Swiftでは大きく2つのフェーズで初期化を行います。
Phase 1
- Designated Initializer または Convenience Initializerが呼ばれる
- クラスに対するメモリが確保される(この段階ではまだメモリは初期化されていない)
- Designated Initializerがプロパティに値をセットし、それに対するメモリを初期化します
- Designated Initializerから親クラスのDesignated Initializerが呼ばれる(移譲)されます
- 親クラスのチェーンを最後まで順々にたどってDesignated Initializerを呼んで処理を行っていきます
- すべてのプロパティに値がセットされPhase 1完了となります
Phase 2
- Designated Initializerの中で
self
を利用してメソッドをコールしたりすることができます(プロパティへのセットももちろん可能) - Convenience Initializerの中で
self
を利用してメソッドをコールしたりすることができます(プロパティへのセットももちろん可能) -
self
を利用したカスタマイズが完了したらPhase 2完了となります
つまり、Designated Initializer, Convenience Initializerそれぞれにおいて処理の順序は
Designated Initializer
- 自身がもつプロパティへ値をセット
- 親クラスのDesignated Initializerを呼び出す
-
self
を使って必要であれば処理を行う
Convenience Initializer
- 自身のDesignated Initializerを呼び出す
-
self
を使って必要であれば処理を行う
となります。
Objective-Cでは誤って不適切なイニシャライザが呼ばれたり、プロパティの初期化が行われていないことが不具合につながることもあるかと思いますが、Swiftではこの辺りが安全な設計によりカバーされています。
さいごに
走り書きっぽく書いてしまったので読みづらい箇所があるかもしれません
記載内容に誤りがある場合はコメントでご指摘いただくか編集リクエストをいただけるととっても嬉しいです ♪