[Swift] 脱初心者 class initializer おさらい(1: designated initializer と convenience initializer)

class 等を定義していると、なんとなくclass の initializer も作れてしまい、コンパイラからのワーニングやエラーに従って修正することでなんとなく使っている人も多いと思います。
改めて、initializer を説明します。

全3回で説明してます。その1回目です。

[Swift] 脱初心者 class initializer おさらい(2: クラス継承と required initializer) [Swift] 脱初心者 class initializer おさらい(3: failable initializer)

initializer とは

Initializer は、class や struct を初期化するために使用されます。

init という名前で定義される関数で、class 等のもつ property を初期設定するために使用されます。

property の設定が終わる前に、使用が開始されてしまうとバグの温床になってしまうため、initializer の処理が終わったときには、すべての property に対して、設定が終了していることが必要です。

以下の説明では、class の initializer に焦点をあてて、説明していきます。

property 概要

property
property とは、class 等の保持する情報を表す変数・定数等のことです。

property 定義例

class SampleClass {
  var informaiton:Int
}
上記は、"SampleClass" というクラスが、"information" という Int 型のプロパティを持つことを定義しています。
このまま使ってしまうと、information というプロパティは、不定の値を持つことになってしまうため、どこかで初期化することが必要となります。

最初に理解すべき3つの initializer

initializer には様々な種類がありますが、まずは以下の3つの initializer を理解することが大事です。

  • default initializer
  • designated initializer
  • convenience initializer

1つ1つ説明していきます。

default initializer

class 定義時に property に対して default 値を指定することができます。

default 値を持つ property を持つ class 定義

class SampleClass {
  var information: Int = 25
}

class の持つすべての property に対して、default 値が設定されているときには、
default initializer というものが自動で用意されます。

自動で用意されるために、init() .... というようなコードはなくとも、下記のように init() として呼び出すことができます。

default initializer 使用例

Let sampleClassInstance = SampleClass.init()

このときには、property には、default 値がセットされます。

designated initializer

class が 初期設定を必要とする property を持つ場合は、その property を初期化する initializer を用意する必要があります。

初期設定を必要とする property とは?

その property が初期設定を必要とするかどうかは、property が、どのように定義されているかで変わります。

  • stored property / computed property のどちらで定義されているか
  • let / var のどちらで定義されているか
  • optional / non-optional のどちらで定義されているか

その property の定義により、initializer で設定が必要になるものならないものがあります。

property default値 設定必要? 設定値
stored property let non-optional default値あり 不要 default 値
stored property let non-optional default値なし 必要 指定値
stored property let optional default値あり 不要 default 値
stored property let optional default値なし 必要 指定値
stored property var optional default値あり 不要 default 値
stored property var optional default値なし 必要 指定値
stored property var non-optional default値あり 不要 default 値
stored property var non-optional default値なし 不要 nil
computed property 不要 設定不可
example code

class SampleClass {
    let a:Int = 3
    let b:Int
    let c:Int? = 3
    let d:Int?
    
    var e:Int = 3
    var f:Int
    var g:Int? = 3
    var h:Int?
    
    var i:Int {
        return 3
    }
    
    init() { // designated initializer
        self.b = 3 // (1)
        self.d = 3 // (2)
        self.f = 4 // (3)
    }
}

上記の designated initializer (init) 内では、設定が必要となるプロパティ (1), (2), (3) のみを設定しています。

設定しないと不定値になってしまうため、コンパイラエラーとなります。なお、var h:Int については、default 値が設定されていませんが、optional Int であるため、nil が設定されます。

convenience initializer

designated initializer の他に、convenience initializer というものを定義することができます。

この convenience initializer は、以下の特徴を持ちます。

  • convenience initializer を用意するかどうかは自由
  • init 処理の一部を delegate できる

名前の通り、convenience (便利)のための initializer です。

initializer 内部では、その class/struct の設定が終了していないため、method 呼び出しや property の参照が制限されますが、
convenience initializer からは、他の initializer (designated initializer, convenience initializer いずれも)呼び出すことができます。
(このことを delegate と呼びます)

このことを使って、例えば初期化処理についても共通処理を導入することができます。

疑問点:いろいろと確認してみました

複数の designated initializer を作っても良い?

答え:作れます (引数が異なる init にする必要があります。)

example code

class SampleClass {
    let a:Int = 3
    let b:Int
    let c:Int? = 3
    let d:Int?
    
    var e:Int = 3
    var f:Int
    var g:Int? = 3
    var h:Int?
    
    var i:Int {
        return 3
    }
    
    init() {
        self.b = 3
        self.d = 3
        self.f = 4
    }
    init(b:Int ) {
        self.b = b
        self.d = 3
        self.f = 4
    }
    init(d:Int) {
        self.b = 3
        self.d = d
        self.f = 4
    }
}
上記では、init(), init(b:), init(d:) が designated initializer となります。

designated initializer を複数 init に分けて作ってもよい?

答え:作れない (convenience initializer のみ、自 class 内の他の init を呼ぶことができます)

2つの designated initializer を作って、1つから他方を呼ぶようにしてみました。

example code

class SampleClass {
    var a:Int
    var b:Int
    init(a: Int) {
        self.a = a
        self.b = 5
    }
    init(b: Int) {
        self.init(a:4) // エラー designated init から 別の init を呼び出し
        self.b = b
    }
}

コンパイル時に以下のエラーが発生します。

error
Designated initializer for 'SampleClass' cannot delegate (with 'self.init'); did you mean this to be a convenience initializer?

(意訳)SampleClass の designated initializer は、delegate できません。convenience initializer を作りたかった?

(訳注)init 内から別の init を呼ぶことを delegate と呼びます。

default 値が設定されているときは、convenience init は、 designated initializer を呼ばなくて良い?

答え:呼ばないといけません。

example code

class SampleClass {
    var a:Int = 4
    var b:Int = 5
    convenience init(a: Int) {
        self.a = a    // error : designated initializer を呼び出していない
    }
}

以下のようなエラーがコンパイル時に発生します。

error
self' used before 'self.init' call or assignment to 'self'
'self.init' isn't called on all paths before returning from initializer

(意訳)self.init を self を使う前に呼ばないといけないけど、self.init が initializer が終了するまでに呼ばれていない

convenience initializer からの designated initializer 呼び出しのタイミングは、自由?

答え:一番最初に、designated initializer を呼ばないといけません。

example code

class SampleClass {
    var a:Int = 3
    var b:Int
    init() {
        self.b = 3
    }
    convenience init(a: Int) {
        self.a = 11    // error : designated initializer 呼び出し前に、self にアクセス
        self.init()
    }
}

以下のようなエラーがコンパイル時に発生します。

error
'self' used before 'self.init' call or assignment to 'self'

(意訳)self が self.init 呼び出しの前に使われました

次の段階で知りたくなる3つの initializer

ここまでの理解で、独自クラスを作って、その initializer を定義することができるようになります。

クラス定義を行って、開発を進めていくと、もう少し凝ったことをやりたくなり、以下のことが必要になることもあります。

  • super.init()
  • failable initializer
  • required initializer

厳密には、super.init は、initializer の種類ではありませんが、親クラスがあるときに、その initializer をどう扱うかということです。

failable initailzier は、失敗することがありえる initializer です。required initializer は、子クラスにたいしての特定の initializer の実装を要求するものです。

詳細は、別記事にする予定です。

まとめ: initializer

オブジェクトや構造体を正しく使用するためには、きちんと初期化されていることが重要です。

そのためには、initializer についてきちんと理解することが必要となります。

initializer 理解のポイント
  • default initializer, designated initializer, convenience initializer の使い分け
  • property 定義と初期化要否の関係

Xcode では、コンパイラが多くをチェックしてくれて、ワーニングやエラーで教えてくれますので、それにしたがって、修正して使うことも可能ですが、上記を理解すると、コンパイラ頼りのコーディングから脱することができます。

個別の項目をきちんと理解することも大切ですが、全体を俯瞰して理解することも大事です。俯瞰した理解には、以下のような書籍を使うのが近道です。

Swift 学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

Swift ポケットリファレンス

Swift を学んでも、プログラミング言語の文法を全て記憶しておくことは無理なので、ちょっとした文法の確認をするために、リファレンス本を手元に置いておくと便利です。

注意
Swift4 までしか対応していないので、理解して参照する必要があります。

おかしな点、不明点ありましたら、お気軽に。

説明は以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です