ライフサイクルの管理
Objective-Cのクラスでは、initがインスタンスの初期化の役割を、deallocがインスタンス破棄に伴う後始末の役割を担っていました。
Swiftのクラスにおいても、initとdeinitが同様の役割を担います。ただし、ストラクチャ、列挙型に関してはdeinitを使うことができません。
イニシャライザ
Objective-Cでインスタンスを生成するときには、イニシャライザであるinitメソッド(あるいは別に定義されたメソッド)を呼び出していました。Swiftもイニシャライザinitを宣言する構文は、第一引数の扱いがメソッドの第二引数と同様になっている部分を除けば、メソッドの宣言と酷似していますが、インスタンスを生成する構文はObjective-Cとは異なり、次のように記述します。
型名(引数のリスト)
例えば、Stringストラクチャでは次のようにイニシャライザが宣言されています。
init(count sz: Int, repeatedValue c: Character)
Stringストラクチャのインスタンスを生成する場合には、次のように記述します。
let string = String(count: 3, repeatedValue: Character("z")) // "zzz"
この例を見てもわかるように、イニシャライザの後に宣言された引数は、プロパティを設定するための引数を提供する役割を果たしています。
値の定まっていない保持プロパティは、イニシャライザで値を定める必要があります。デフォルト値が設定されているプロパティに、イニシャライザで値を代入する必要はありません。
オプショナル列挙型の保持プロパティは、デフォルト値としてnil(=Optional.None)が自動的に代入されるため、イニシャライザで値を代入する必要はありません。
定数プロパティも、イニシャライザ内であれば値を代入可能です。また、willSet/didSetで監視しているプロパティの値をイニシャライザの中で変えても、willSet/didSetは呼ばれません。
また、イニシャライザは単一の型に対して複数宣言することができます。
イニシャライザを複数宣言することで、イニシャライザの中からイニシャライザを呼び出すことは、一定のルールに基づけば可能です。しかし、イニシャライザからインスタンスメソッドを呼び出すことはできません。イニシャライザの中からインスタンスプロパティを読み込むこともできません。
今までに登場したプロパティは全てインスタンスプロパティとして扱われますが、それとは別にSwiftでは型に紐付くプロパティとしてタイププロパティを宣言することができます。クラスに対してはclassを、ストラクチャ、列挙型に対してはstaticをvar/letの前につけることでそれぞれタイププロパティとして認識されます。メソッドに関しても同様の方法でfuncの前にclass, staticをつけることでタイプメソッドとして型に紐付くメソッドを宣言することができます。
デフォルトイニシャライザ
基底クラスとストラクチャにおいて、すべてのプロパティにデフォルト値が設定されていて、かつイニシャライザを定義していない場合には、引数を介して値を与えることなくインスタンスを生成できるイニシャライザが自動的に生成されます。これをデフォルトイニシャライザといいます。
次の例では、すべてのプロパティにデフォルト値が設定されているため、デフォルトイニシャライザによりインスタンスが生成されます。
class SomeClass {
var name: String = "defaultName"
var age: Int = 0
var companyName: String? // オプショナル列挙型はnilをデフォルト値として持つ
}
let someClass = SomeClass() // デフォルトイニシャライザを用いたインスタンス生成
プロパティ名付きのイニシャライザ
ストラクチャでは、独自のイニシャライザを宣言しておらず、すべてのプロパティにデフォルト値を設定していない場合には、次の例のようにプロパティ名付きのイニシャライザが自動的に生成されます。
struct SomePoint {
var x: Double
var y: Double
var z: Double
}
// init(x: Double, y: Double, z: Double)が自動生成される
let point = SomePoint(x: 1, y: 1, z: 1) // x: 1.0, y: 1.0, z: 1.0
ストラクチャにおけるイニシャライザの委譲
ストラクチャのイニシャライザは、self経由で他のイニシャライザから呼ばれる場合があります。これをイニシャライザの委譲といいます。次のコードはイニシャライザの委譲が行われている例です。
struct CustomRange {
let start: Int = 0
var length: UInt = 0
init(){}
init(start: Int, length: UInt) {
self.start = start
self.length = length
}
init (end: Int, length: UInt) {
var start = end - Int(length) + 1
self.init(start: start, length: length)
}
}
このイニシャライザの委譲はストラクチャのみならず、列挙型でも行うことができます。
クラスのイニシャライザにまつわるルール
クラスのイニシャライザの中からイニシャライザを呼び出す際には一定のルールがあります。
Objective-Cでもイニシャライザの中からイニシャライザを呼び出すことはできましたが、外部から見た時、それが継承元のイニシャライザを呼び出しているのか、それとも内部のイニシャライザから呼び出しているのかを、言語の機構として判別する手段はありませんでした。Swiftでは、convenienceキーワードによって、イニシャライザを挙動をより正確に把握できるようになりました。
Swiftのクラスのイニシャライザには、指定イニシャライザとコンビニエンスイニシャライザの2種類があります。どちらのイニシャライザであるかで、内部で呼び出せるイニシャライザに制限があります。両者の違いは次のとおりです。
指定イニシャライザ
通常の宣言方法によるイニシャライザです。指定イニシャライザからは、継承元(スーパークラス)の指定イニシャライザしか呼び出せません。
コンビニエンスイニシャライザ
convenienceキーワードを付けて宣言します。コンビニエンスイニシャライザからは、同じクラス(それが宣言されているクラス)のイニシャライザしか呼び出せません。コンビニエンスイニシャライザをたどって行くと、最終的に指定イニシャライザに行き着く必要があります。
例を見てみましょう。
class A { // 基底クラス
var index: Int
init(index: Int) { // 基底クラスの指定イニシャライザ
self.index = index
}
convenience init() { // コンビニエンスイニシャライザ
self.init(index: 0) // 同じクラスのイニシャライザを呼び出す
}
}
class B : A { // Aクラスのサブクラス
var name: String
init(index: Int, name: String) { // サブクラスの指定イニシャライザ
self.name = name
super.init(index: index) // 継承元の指定イニシャライザを呼び出す
}
convenience init(index: Int) { // コンビニエンスイニシャライザ
self.init(index: index, name: "default") // 同じクラスのイニシャライザを呼び出す
}
}
クラスBの指定イニシャライザinit(index: Int, name: String)からは、継承元であるクラスAの指定イニシャライザしか呼び出せません。このときにはsuper経由で呼び出します。
また、convenienceキーワードが付いたクラスAのinit()、クラスBのinit(index: Int)からは、同じクラスのイニシャライザしか呼び出せません。
なお、継承に際してのイニシャライザの挙動はメソッドとほぼ同じですが、スーパークラスの指定イニシャライザを、サブクラスでコンビニエンスイニシャライザとして扱うこともできます。上記の例では、クラスBのinit(index: Int)メソッドがそれに該当します。
また、スーパークラスの指定イニシャライザの呼び出しは、サブクラスの保持プロパティの値が定まっていることを確認してから行われます。サブクラスの保持プロパティの値がすべて定まらないうちに継承元の指定イニシャライザを呼び出すとエラーになります。上記の例では、Bクラスのinit(index: Int, name: String)での指定イニシャライザは、nameプロパティの値が定まってからでないと呼び出せません。
サブクラスの指定イニシャライザに実装を強制するためのrequiredキーワードもあります。イニシャライザ宣言の前にrequiredと置くことでサブクラスにイニシャライザをオーバーライドすることを強制します。
class A { // 基底クラス
var name: String
required init(name: String) {
self.name = name
}
}
class B : A { // Aクラスのサブクラス
init(name: String) { // サブクラスでの指定イニシャライザの実装を強制できる
super.init(name: name)
}
}
上の例で、Bクラスの指定イニシャライザを実装しない場合はエラーとなります。
デイニシャライザ
Objective-Cのdeallocメソッドと同様に、Swiftには、クラスのインスタンス破棄時に呼ばれるデイニシャライザdeinitがあります。
class SomeClass {
deinit {
print("instance destroyed! ")
}
}
var classes = [SomeClass(), SomeClass()]
classes = [] // instance destroyed! instance destroyed!
deinitはクラスに対してのみ宣言可能です。クラスのインスタンスが破棄されたことに伴う処理(ファイルマネージャクラスの破棄に伴う書き込み中データの破棄など)を特別に行いたいときに有効です。ただし、deinitはARC(Automatic Reference Counting)によるメモリ管理で自動的に呼ばれるものであって、メモリを解放するために自発的に呼び出すことはできないので注意してください。
次回は
今回は、クラスとストラクチャに的を絞ってプロパティ、メソッド、継承、ライフサイクルの説明を行いましたが、いかがでしたでしょうか。次回は「subscript」「タイプキャスト」「ARC」を中心に説明した後、クラスとストラクチャおよび列挙型のまとめを行います。お楽しみに。
