[Swift] クラスのイニシャライザ3: Designated Initializer と Convenience Initializer

More than 1 year has passed since last update.

はじめに

この記事では、designated initializer と convenience initializer について説明します。

他の記事へのリンク

環境

  • Xcode 6.1 (Playground)

1. クラスのイニシャライザには2種類ある

クラスのイニシャライザには、以下の2種類があります。

  • Designated initializer
  • Convenience initializer

2. Designated Initializer とは?

2.1. 役割

基本となるイニシャライザです。

2.2. 書き方

init () {
    ...
}

2.3. 必須か否か

クラスに1つ以上必要です。

注)
詳細は後述

2.4. 他のイニシャライザの呼び出し

  • スーパークラスの designated initializer を呼び出す

もしくは、

  • 他のイニシャライザを一切呼び出さない

のいずれかです。

注)
詳細は後述

2.5. 二段階の初期化処理

Designated initializer の中では、以下の二段階で初期化処理を実装する必要があります。

init() {
    フェーズ1自分とスーパークラスのプロパティの初期化
    フェーズ2自分とスーパークラスのプロパティのカスタマイズ
}

注)
詳細は後述

3. Convenience Initializer とは?

3.1. 役割

Designated initializer の補助的な役割を果たします。

3.2. 書き方

convenience init() {
    ...
}

3.3. 必須か否か

必須ではありません。

3.4. 他のイニシャライザの呼び出し

必ず同じクラスの designated initializer か convenience initializer を呼び出す必要があります。

注)
詳細は後述

3.5. 二段階の初期化処理

Designated initializer と同様に、convenience initializer の中でも以下の二段階で初期化処理を実装する必要があります。

convenience init() {
    フェーズ1自分とスーパークラスのプロパティの初期化
    フェーズ2自分とスーパークラスのプロパティのカスタマイズ
}

注)
詳細は後述

4. Designated Initializer は必須

Designated initializer は、クラスに1つ以上実装する必要があります。実装しないとエラーが発生します。

//
// error: class 'Animal' has no initializers
//
class Animal {
    let name: String
}

4.1. 全てのプロパティがデフォルト値を持っている場合

ただし、全てのプロパティがデフォルト値を持っている場合は、明示的にイニシャライザを実装する必要はありません。暗黙的に designated initializer が実装されます。

このとき、暗黙的に実装されるイニシャライザのことを、「default initializer」と言います。

//
// 全てのプロパティにデフォルト値が設定されているので、
// 暗黙的に default initializer "init() {}" が実装される
//
class Animal {
    var name: String = "unknown"
}

//
// Default initializer "init()" を呼び出している
//
var a = Animal()

4.2. イニシャライザが自動的に継承される場合

サブクラスの場合はイニシャライザが自動的に継承される場合があります(継承される条件は後述)。その場合、明示的にイニシャライザを実装する必要はありません。

以下の例では、designated initializer がスーパークラスから継承されています。

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }
}

//
// スーパークラス Animal から "init(name: String)" が継承される
//
class Cat: Animal {}

//
// スーパークラス Animal から継承された "init(name: String)" を呼び出している
//
var c = Cat(name: "kitty")

5. Designated Initializer はスーパークラスの Designated Initializer を呼び出すことができる

Designated initializer はスーパークラスの designated initializer を呼び出すことができます。

d2super_d.png

class Animal {
    let name: String

    //
    // 呼び出される側
    //    
    init(name: String) {
        self.name = name
    }
}

class Cat: Animal {
    //
    // 呼び出す側
    //
    init() {
        super.init(name: "kitty")
    }
}

var c = Cat()
println(c.name) // -> kitty

しかし、スーパクラスの convenience initializer を呼び出すことはできません。

d2super_c.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // 呼び出される側
    //
    convenience init() {
        self.init(name: "unknown")
    }
}

class Cat: Animal {
    //
    // 呼び出す側
    //
    init() {
        super.init() // -> error: must call a designated initializer of the superclass 'Animal'

    }
}

また、同じクラスのイニシャライザを呼び出すこともできません。

d2self_d.png

class Animal {
    let name: String

    //
    // 呼び出される側
    //
    init(name: String) {
        self.name = name
    }

    //
    // 呼び出す側
    //
    init() {
        self.init(name: "unknown") // -> error: designated initializer for 'Animal' cannot delegate (with 'self.init'); did you mean this to be a convenience initializer?
    }
}

d2self_c.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // 呼び出される側
    //
    convenience init() {
        self.init(name: "unknown")
    }

    //
    // 呼び出す側
    //
    init(nickname: String) {
        self.init() // -> error: designated initializer for 'Animal' cannot delegate (with 'self.init'); did you mean this to be a convenience initializer?
    }
}

他のイニシャライザを呼び出さなくても問題はありません。

notCall_d.png

class Animal {
    let name: String

    init() {
        self.name = "unknown"
    }
}

6. Convenience Initializer は同じクラスのイニシャライザを呼び出すことができる

Convenience initializer は同じクラスのイニシャライザを呼び出すことができます。

c2self_d.png

class Animal {
    let name: String

    //
    // 呼び出される側
    //
    init(name: String) {
        self.name = name
    }

    //
    // 呼び出す側
    //
    convenience init() {
        self.init(name: "unknown")
    }
}

var a = Animal()
println(a.name) // -> unknown

c2self_c.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // 呼び出される側
    //
    convenience init(nickname: String) {
        self.init(name: nickname)
    }

    //
    // 呼び出す側
    //
    convenience init() {
        self.init(nickname: "kitty")
    }
}

var a = Animal()
println(a.name) // -> kitty

同じクラスのイニシャライザを呼び出さないとエラーが発生します。

notCall.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // error: self.init isn't called on all paths in delegating initializer
    //
    convenience init() {
    }
}

また、designated initializer と違い、スーパークラスのイニシャライザを呼び出すことはできません。

c2super_d.png

class Animal {
    let name: String

    //
    // 呼び出される側
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    //
    // 呼び出す側
    //
    convenience init(name: String) {
        super.init() // -> error: Convenience initializer for 'Cat' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
    }
}

c2super_c.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // 呼び出される側
    //
    convenience init() {
        self.init(name: "unknown")
    }
}

class Cat: Animal {
    //
    // 呼び出す側
    //
    convenience init(nickname: String) {
        super.init() // -> error: must call a designated initializer of the superclass 'Animal'
    }
}

7. Convenience Initializer は、最終的に Designated Initializer を呼び出さなければならない

Convenience initializer は、最終的に designated initializer を呼び出さなければいけません。これを守らないと、無限ループが発生してしまいます。

Convenience initializer は、必ず他のイニシャライザを呼ぶ必要があります。そのため、convenience initializer が convenience initializer だけを呼んでいると、結果として無限ループが発生してしまいます。

Designated initializer の場合は、「他のイニシャライザを一切呼び出さない」ということが可能なので、最終的に designated initializer を呼び出せば、無限ループに陥ることはありません。

注)
最終的に designated initializer を呼び出さなくても、コンパイラはエラーを出力しないので注意が必要です。

final_d.png

finalNot_d.png

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    //
    // A
    //
    convenience init() {
        //
        // B を呼び出す
        //
        self.init(nickname: "unknown")
    }

    //
    // B
    //
    convenience init(nickname: String) {
        //
        // A を呼び出す
        //
        self.init()
    }
}

//
// A を呼び出す
//
var a = Animal() // -> 無限ループに陥る

8. 二段階の初期化処理

イニシャライザ内では、以下の二段階の初期化処理を正しい順番で実装する必要があります。

  • フェーズ1 : 自分とスーパークラスのプロパティの初期化
  • フェーズ2 : 自分とスーパークラスのプロパティのカスタマイズ

これは、プロパティが意図しない形で上書きされてしまうことを防ぐためのルールです。

8.1. プロパティが意図せず上書きされてしまう例

例えば、以下のコードでは、スーパークラスの初期化が完了する前にスーパークラスをカスタマイズしているので、意図したとおりに値が設定できません。

class Animal {
    var name: String

    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    init(name: String) {
        //
        // スーパークラスのプロパティのカスタマイズ
        //
        self.name = name

        //
        // スーパークラスのプロパティの初期化
        //
        super.init()
    }
}

var c = Cat(name: "kitty")
println(c.name) // -> unknown (実際にはコンパイラエラーになり、コンパイルできない。イニシャライザ内では、正しい順番で処理を実装しないとコンパイラエラーが発生する。詳細は後述)

以下の順番に直せば、意図どおりにプロパティが設定されます

class Animal {
    var name: String

    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    init(name: String) {
        //
        // スーパークラスのプロパティの初期化
        //
        super.init()

        //
        // スーパークラスのプロパティのカスタマイズ
        //
        self.name = name
    }
}

var c = Cat(name: "kitty")
println(c.name) // -> kitty

上記のように、イニシャライザの中では初期化処理を正しい順番で実行しないと、プロパティが意図しない形で上書きされてしまいます。

そのため、swift では「二段階の初期化処理」というものを定義し、イニシャライザ内の処理の順番を厳格に定めています。

8.2. 二段階の初期化処理の具体的な中身

二段階の初期化処理を、より細かい粒度で見ると以下のようになります。

注)
Swift では、以下の正しい順番が破られていた場合に、コンパイラがエラーを出力してくれるようになっています。そのため、この順番を覚える必要はありません。

8.2.1. Designated Initializer の場合

init() {
    ///////////////////////////////////////////////////////////
    // フェーズ1、自分とスーパークラスのプロパティの初期化
    ///////////////////////////////////////////////////////////

    //
    // 1、自分のプロパティの初期化
    //
    self.property_of_self = "Initialization"

    //
    // 2、スーパークラスのイニシャライザの呼び出し
    //
    super.init()


    ///////////////////////////////////////////////////////////
    // フェーズ2、自分とスーパークラスのプロパティのカスタマイズ
    ///////////////////////////////////////////////////////////

    //
    // 3、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
    //
    self.property_of_self  = "Customization"
    self.property_of_super = "Customization"
    self.instance_method_of_self()
    self.instance_method_of_super()
}

注意) プロパティの「初期化」と「カスタマイズ」の違い

1番目がプロパティの「初期化」で、

上記のコードを一部抜粋の上、再掲
    //
    // 1、自分のプロパティの初期化
    //
    self.property_of_self = "Initialization"

3番目がプロパティの「カスタマイズ」であることに注意してください。

上記のコードを一部抜粋の上、再掲
    //
    // 3、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
    //
    self.property_of_self  = "Customization"
    self.property_of_super = "Customization"

例えば、以下のコードでは定数 nickname を正常に初期化することができます。

class Animal {
}

class Cat: Animal {
    let nickname: String

    override init() {
        ///////////////////////////////////////////////////////////
        // フェーズ1、自分とスーパークラスのプロパティの初期化
        ///////////////////////////////////////////////////////////

        //
        // 1、自分のプロパティの初期化
        //
        self.nickname = "Initialization"

        //
        // 2、スーパークラスのイニシャライザの呼び出し
        //
        super.init()

        ///////////////////////////////////////////////////////////
        // フェーズ2、自分とスーパークラスのプロパティのカスタマイズ
        ///////////////////////////////////////////////////////////      

        //
        // 3、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
        //
    }
}

しかし、self.nickname = "Initialization" を1番目から3番目に移動しただけの以下のコードでは、定数 nickname を初期化することができません。

class Cat: Animal {
    let nickname: String

    override init() {
        ///////////////////////////////////////////////////////////
        // フェーズ1、自分とスーパークラスのプロパティの初期化
        ///////////////////////////////////////////////////////////

        //
        // 1、自分のプロパティの初期化
        //

        //
        // 2、スーパークラスのイニシャライザの呼び出し
        //
        super.init() // -> error: property 'self.nickname' not initialized at super.init call

        ///////////////////////////////////////////////////////////
        // フェーズ2、自分とスーパークラスのプロパティのカスタマイズ
        ///////////////////////////////////////////////////////////

        //
        // 3、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
        //
        self.nickname = "Initialization"

    }
}

これは、1番目がプロパティの「初期化」をする場所で、3番目がプロパティの「カスタマイズ」をする場所だからです。1番目でプロパティが初期化されていないと、エラーが発生してしまいます。

イニシャライザ内の処理を実装する際は、プロパティの「初期化」と「カスタマイズ」の違いに注意してください。

8.2.2. Convenience Initializer の場合

convenience init() {
    ///////////////////////////////////////////////////////////
    // フェーズ1、自分とスーパークラスのプロパティの初期化
    ///////////////////////////////////////////////////////////

    //
    // 1、同じクラスの他のイニシャライザの呼び出し
    //
    self.init()


    ///////////////////////////////////////////////////////////
    // フェーズ2、自分とスーパークラスのプロパティのカスタマイズ
    ///////////////////////////////////////////////////////////

    //
    // 2、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
    //
    self.property_of_self  = "Customization"
    self.property_of_super = "Customization"
    self.instance_method_of_self()
    self.instance_method_of_super()
}

注意) Convenience Initializer の中ではプロパティを初期化できない

2番目で行うのがプロパティの「初期化」ではなく、「カスタマイズ」であることに注意してください。

上記のコードを一部抜粋の上、再掲
    //
    // 2、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
    //
    self.property_of_self  = "Customization"
    self.property_of_super = "Customization"

そのため、以下のコードのとおり、convenience initializer ではプロパティの「初期化」ができません。

class Animal {
    let name: String

    init() {
    }

    convenience init(name: String) {
        ///////////////////////////////////////////////////////////
        // フェーズ1、自分とスーパークラスのプロパティの初期化
        ///////////////////////////////////////////////////////////

        //
        // 1、同じクラスの他のイニシャライザの呼び出し
        //
        self.init()


        ///////////////////////////////////////////////////////////
        // フェーズ2、自分とスーパークラスのプロパティのカスタマイズ
        ///////////////////////////////////////////////////////////

        //
        // 2、自分とスーパークラスのプロパティのカスタマイズ or インスタンスメソッドの呼び出し
        //
        self.name = "Initialization" // -> error: cannot assign to 'name' in 'self'

    }
}

プロパティを初期化する場合は、「プロパティの宣言時にデフォルト値を設定する」か、

class Animal {
    let name: String = "Initialization"
}

「Designated initializer の1番目の箇所で初期化する」のいずれかで初期化してください。

class Animal {
    let name: String

    init() {
        ///////////////////////////////////////////////////////////
        // フェーズ1、自分とスーパークラスのプロパティの初期化
        ///////////////////////////////////////////////////////////

        //
        // 1、自分のプロパティの初期化
        //
        self.name = "Initialization"

        ...
    }
}

9. イニシャライザの自動的な継承

以下のルール1、もしくは、ルール2の条件を満たすと、スーパークラスのイニシャライザがサブクラスに自動的に継承されます。

9.1. Designated Initializer の自動的な継承

ルール1)

サブクラスが designated initializer を1つも実装していなかった場合、スーパークラスの全ての designated initializer が自動的に継承されます。

9.1.1. 例) サブクラスが Designated Initializer を1つも実装していなかった場合

サブクラスが designated initializer を1つも実装していなかった場合、

DNoInit1.png

スーパークラスの全ての designated initializer が自動的に継承されます。

DNoInit2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

9.1.2. 例) サブクラスが Convenience Initializer だけを実装していた場合

サブクラスが convenience initializer だけを実装していた場合、

DCInit1.png

Designated initializer が1つも実装されていない状態なので、スーパークラスの全ての designated initializer が自動的に継承されます。

DCInit2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    //
    // Y
    //
    convenience init(firstName: String, lastName: String) {
        //
        // スーパークラスから自動的に継承された A を呼び出している
        //
        self.init(name: firstName + " " + lastName)
    }
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

//
// Y を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

9.1.3. 例) サブクラスの Convenience Initializer がスーパークラスの Designated Initializer の一部をオーバーライドしていた場合

サブクラスの Convenience Initializer がスーパークラスの Designated Initializer の一部をオーバーライドしていた場合、

DCOverride1.png

Designated initializer が1つも実装されていない状態なので、スーパークラスの全ての designated initializer が自動的に継承されます。

DCOverride2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    //
    // A'
    //
    override convenience init(name: String) {
        //
        // スーパークラスから自動的に継承された B を呼び出している
        //
        self.init()
    }
}

//
// A' を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

9.1.4. 例) サブクラスの Designated Initializer が、スーパークラスの Designated Initializer を一部だけオーバーライドしていた場合

サブクラスの designated initializer が、スーパークラスの designated initializer を一部だけオーバーライドしていた場合、

DOnlyOneOverride1.png

サブクラスに designated initializer が実装されてしまっている状態なので、スーパークラスの designated initializer は自動的に継承されません。

DOnlyOneOverride2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    //
    // A'
    //
    override init(name: String) {
        super.init(name: name)
    }
}

//
// A' を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> error

9.1.5. 例) サブクラスが、スーパークラスの Designated Initializer とは別の Designated Initializer を実装していた場合

サブクラスが、スーパークラスの designated initializer とは別の designated initializer を実装していた場合、

DOtherInit1.png

サブクラスに designated initializer が実装されてしまっている状態なので、スーパークラスの designated initializer は自動的に継承されません。

DOtherInit2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }
}

class Cat: Animal {
    //
    // X
    //
    init(firstName: String, lastName: String) {
        super.init(name: firstName + " " + lastName)
    }
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> error

//
// B を呼び出す
//
var c2 = Cat() // ->  error

//
// X を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

9.2. Convenience Initializer の自動的な継承

ルール2)

サブクラスが、スーパークラスの designated initializer を全て実装していた場合(自動的に継承された場合も含む)、convenience initializer も自動的に継承されます。

9.2.1. 例) サブクラスがイニシャライザを1つも実装していなかった場合

サブクラスがイニシャライザを1つも実装していなかった場合、

CNoInit1.png

Designated initializer が1つも実装されていない状態なので、スーパークラスの全ての designated initializer が自動的に継承されます(ルール1)。

さらに、サブクラスがスーパークラスの全ての designated initializer を実装している状態なので、convenience initializer も自動的に継承されます(ルール2)。

CNotInit2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

9.2.2. 例) サブクラスが、スーパークラスとは別の Convenience Initializer を実装していた場合

サブクラスが、スーパークラスとは別の convenience initializer を実装していた場合、

COtherC1.png

Designated initializer が1つも実装されていない状態なので、スーパークラスの全ての designated initializer が自動的に継承されます(ルール1)。

さらに、サブクラスがスーパークラスの全ての designated initializer を実装している状態なので、convenience initializer も自動的に継承されます(ルール2)。

COtherC2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
    //
    // Y
    //
    convenience init(nickname: String) {
        //
        // スーパークラスから自動的に継承された A を呼び出す
        //
        self.init(name: nickname)
    }
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

//
// Y を呼び出す
//
var c4 = Cat(nickname: "kitty") // -> OK

9.2.3. 例) サブクラスの Designated Initializer が、スーパークラスの全ての Designated Initializer をオーバーライドしていた場合

サブクラスの designated initializer が、スーパークラスの全ての designated initializer をオーバーライドしていた場合、

CAllOverride1.png

サブクラスがスーパークラスの全ての designated initializer を実装している状態なので、convenience initializer も自動的に継承されます(ルール2)。

CAllOverride2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
    //
    // A'
    //
    override init(name: String) {
        super.init(name: name)
    }

    //
    // B'
    //
    override init() {
        super.init()
    }
}

//
// A' を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B' を呼び出す
//
var c2 = Cat() // -> OK

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

9.2.4. 例) サブクラスの Convenience Initializer がスーパークラスの Designated Initializer の一部をオーバーライドしていた場合

サブクラスの Convenience Initializer がスーパークラスの Designated Initializer の一部をオーバーライドしていた場合、

CCOverride1.png

Designated initializer が1つも実装されていない状態なので、スーパークラスの全ての designated initializer が自動的に継承されます(ルール1)。

さらに、サブクラスがスーパークラスの全ての designated initializer を実装している状態なので、convenience initializer も自動的に継承されます(ルール2)。

CCOverride2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
    //
    // A'
    //
    override convenience init(name: String) {
        //
        // スーパークラスから自動的に継承された B を呼び出している
        //
        self.init()
    }
}

//
// A' を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> OK

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> OK

9.2.5. 例) サブクラスが、スーパークラスの Designated Initializer を一部しかオーバーライドしていなかった場合

サブクラスが、スーパークラスの designated initializer を一部しかオーバーライドしていなかった場合、

COnlyOneOverride1.png

サブクラスに designated initializer が実装されてしまっている状態なので、スーパークラスの designated initializer は自動的に継承されません(ルール1)。

さらに、サブクラスがスーパークラスの全ての designated initializer を実装していないので、convenience initializer も自動的に継承されません(ルール2)。

COnlyOneOverride2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
    //
    // A'
    //
    override init(name: String) {
        super.init(name: name)
    }
}

//
// A' を呼び出す
//
var c1 = Cat(name: "kitty") // -> OK

//
// B を呼び出す
//
var c2 = Cat() // -> error

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> error

9.2.6. 例) サブクラスが、スーパークラスとは別の Designated Initializer を実装していた場合

サブクラスが、スーパークラスとは別の designated initializer を実装していた場合、

COtherD1.png

サブクラスに designated initializer が実装されてしまっている状態なので、スーパークラスの designated initializer は自動的に継承されません(ルール1)。

さらに、サブクラスがスーパークラスの全ての designated initializer を実装していないので、convenience initializer も自動的に継承されません(ルール2)。

COtherD2.png

class Animal {
    let name: String

    //
    // A
    //
    init(name: String) {
        self.name = name
    }

    //
    // B
    //
    init() {
        self.name = "unknown"
    }

    //
    // C
    //
    convenience init(firstName: String, lastName: String) {
        self.init(name: firstName + " " + lastName)
    }
}

class Cat: Animal {
    //
    // X
    //
    init(firstName: String, middleName: String, lastName: String) {
        super.init(name: firstName + " " + middleName + " " + lastName)
    }
}

//
// A を呼び出す
//
var c1 = Cat(name: "kitty") // -> error

//
// B を呼び出す
//
var c2 = Cat() // ->  error

//
// C を呼び出す
//
var c3 = Cat(firstName: "John", lastName: "Smith") // -> error

//
// X を呼び出す
var c4 = Cat(firstName: "John", middleName: "T.", lastName: "Smith") // -> OK

参考文献

The Swift Programming Language

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.