Help us understand the problem. What is going on with this article?

Swiftのイニシャライザについて

More than 3 years have passed since last update.

はじめに

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
//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
//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
  1. Designated Initializer または Convenience Initializerが呼ばれる
  2. クラスに対するメモリが確保される(この段階ではまだメモリは初期化されていない)
  3. Designated Initializerがプロパティに値をセットし、それに対するメモリを初期化します
  4. Designated Initializerから親クラスのDesignated Initializerが呼ばれる(移譲)されます
  5. 親クラスのチェーンを最後まで順々にたどってDesignated Initializerを呼んで処理を行っていきます
  6. すべてのプロパティに値がセットされPhase 1完了となります
Phase 2
  1. Designated Initializerの中でselfを利用してメソッドをコールしたりすることができます(プロパティへのセットももちろん可能)
  2. Convenience Initializerの中でselfを利用してメソッドをコールしたりすることができます(プロパティへのセットももちろん可能)
  3. selfを利用したカスタマイズが完了したらPhase 2完了となります

つまり、Designated Initializer, Convenience Initializerそれぞれにおいて処理の順序は

Designated Initializer
  1. 自身がもつプロパティへ値をセット
  2. 親クラスのDesignated Initializerを呼び出す
  3. selfを使って必要であれば処理を行う
Convenience Initializer
  1. 自身のDesignated Initializerを呼び出す
  2. selfを使って必要であれば処理を行う

となります。

Objective-Cでは誤って不適切なイニシャライザが呼ばれたり、プロパティの初期化が行われていないことが不具合につながることもあるかと思いますが、Swiftではこの辺りが安全な設計によりカバーされています。

さいごに

走り書きっぽく書いてしまったので読みづらい箇所があるかもしれません :bow:
記載内容に誤りがある場合はコメントでご指摘いただくか編集リクエストをいただけるととっても嬉しいです :smile:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away