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

     

TAGS:

⌛️ 2 min.
class の動作を理解するためには、initializer を理解することがポイントの1つです。

class を継承するときの initializer を説明します。

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

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

クラスの継承

Initializer で初期化が必要な property を持つ親クラス、子クラスを以下のように定義しました。

ParentClass と ChildClass


import Foundation

class ParentClass {
    var pProp:Int
    
    init(_ value: Int) {
        pProp = value
    }
    
    convenience init(value:Int, coeff: Int) {
        self.init(value * coeff)
    }
    
}

class ChildClass : ParentClass {
    var cProp:Int
}

ParentClass には、convenience initializer も定義しています。

ChildClass には、initializer を定義していませんので、現時点ではコンパイルでエラーになります。

コード

Class 'ChildClass' has no initializers

(意訳) ChildClass に initializer がありません。

子クラス側での考え方

initializer によって初期化が必要な property は、親クラスの持つ property と子クラスの持つ property です。

子クラス自身の持つ property の初期化

クラスの initizlier の考え方がそのまま適用されます。

つまり、初期化が必要な property を初期化するための initializer の定義が必要となります。

ChildClass で初期化が必要な property は、cProp ですので、以下のような initializer を作成し、designated initializer としました。

ChildClass に designated initializer を定義


class ChildClass : ParentClass {
    var cProp:Int
    init(cValue: Int) {
        cProp = cValue
    }
}

現時点では、ChildClass の property だけしか初期化されていませんので、エラーとなります。

コード

'super.init' isn't called on all paths before returning from initializer

(意訳) super.init が呼ばれていない

親クラス(ParentClass) の property も初期化が必要ですので、このエラーの理由は明らかです。

親クラスの持つ property の初期化

ChildClass の initializer から、親クラスの property を直接設定することはできません。子クラスの initializer から 親クラスの initializer を呼ぶことで、親クラスの property を初期化します。

親子クラスでの initializer のポイント

子クラスの initializer では、親クラスの initializer を呼ぶことで、親クラスの property を初期化する

example code


class ChildClass : ParentClass {
    var cProp:Int
    
    init(pValue:Int, cValue: Int) {
        cProp = cValue
        super.init(pValue)
    }
}

ここでポイントが1つあります。

Super.init は、子クラスの property の初期化に呼ぶ必要があります。

親子クラスでの initializer のポイント

子クラスの initializer では、親クラスの initializer は、子クラス property の初期化後に呼ぶ

順序を逆にするとエラーとなります。

エラーの発生するコード


class ChildClass : ParentClass {
    var cProp:Int
    
    init(pValue:Int, cValue: Int) {
        super.init(pValue)  // error : Property 'self.cProp' not initialized at super.init call
        cProp = cValue
    }
}

designated initializer と convenience initializer

クラスの initializer では、convenience initializer を定義することができましたが、その中からは designated initializer をコールする必要がありました。

親子クラスでの initializer のポイント

子クラスの designated initializer からは、親クラスの designated initializer を呼ぶ

実際に、子クラスから、親クラスの convenience initializer を呼ぶとエラーとなります。

example code


class ChildClass : ParentClass {
    var cProp:Int
    
    init(pValue:Int, cValue: Int) {
        cProp = cValue
        super.init(value: 3, coeff: 4) // error : Must call a designated initializer of the superclass 'ParentClass'
    }
}

親クラス側から指定できること

親クラスでの定義で、子クラス側に、initializer の override が必要であることを指定することができます。

これは、”required” というキーワードと共に initializer を定義することで指定します。

この “required” initializer は、designated initializer / convenience initializer のどちらにも指定することが可能です。

子クラス側で実装するときに、同じタイプ(designated initializer/ convenience initializer)であることは必要ありません

どんなときに使われる?

Storyboard を使って UI を作成した場合には、作成した要素は、storyboard に保存され、実行時に読み込まれます。

保存する時や実行時に、encode/decode オブジェクトとのやり取りが必要になりますので、そのための initializer の定義が必須となります。
そのため、UIKit のクラスの多くが、以下のような定義を持っています。

UIKit でよくみる initializer


required init?(coder aDecoder: NSCoder) {
  // ....
}

こんな場合は?

親 class の property を、子 class の initializer で設定できる?

答え:できません。アクセスしようとすると、親クラスの property が、super.init 実行前に使用されたとエラーになります。

example code


class ChildClass : ParentClass {
    var cProp:Int
    
    init(pValue:Int, cValue: Int) {
        cProp = cValue
        self.pProp = cValue // error: 'self' used in property access 'pProp' before 'super.init' call
        super.init(3)
    }
}

親 class の convenience initializer を、子 class の initializer から呼んでも良い?

答え:できません。designated initializer を呼んでくださいというエラーになります。

example code


class ChildClass : ParentClass {
    var cProp:Int
    
    init(pValue:Int, cValue: Int) {
        cProp = cValue
        super.init(value: 3, coeff: 4) // error: Must call a designated initializer of the superclass 'ParentClass'
    }
}

まとめ:クラス継承時の initializer

継承関係のクラスの initializer を考える上では以下の点がポイントとなります。

initializer のポイント
  • 親クラス、子クラス それぞれの property はそれぞれが初期化する
  • 子クラスの designated initializer から呼び出すことができるのは、親クラスの designated initializer だけ
  • override されることが必須の initializer には、required キーワードを設定する

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

Swift 学習におすすめの本

詳解Swift

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

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

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

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

Swift ポケットリファレンス

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

注意

Swift4 までしか対応していないので、理解して参照する必要があります。Swift 5 に対応したリファレンス本がでるまでは、一番有用です。

説明は以上です。
不明な点、おかしな点は、お気軽にご連絡ください。

コメントを残す

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