【Swift】型の構成要素〜イニシャライザ〜

#イニシャライザ
イニシャライザは、型のインスタンスを初期化します。

**全てのプロパティは、インスタンス化の完了までに値が代入されていなければなりません。**

なので、プロパティの宣言時に初期値を持たないプロパティは、
イニシャライザ内で初期化する必要があります。

レイジーストアドプロパティは、アクセスするまで生成されないので、
初期値を持っていない状態とは少し違います。

##定義方法

イニシャライザは、`initキーワード`で宣言します。
定義内容としては、**引数と初期化に関する処理**を定義します。

イニシャライザの引数の文法は、関数の引数と同じです。

```swift

init(引数) {
   初期化処理
}
```

例として構造体Sampleを作成してみました。

`init(a: String) { }`の部分がイニシャライザになります。
String型の引数を一つ受け取っています。

`self.a = a`の`self a`はプロパティaを指します。
右辺のaは引数名のaを指します。

この処理でプロパティaの中には引数の値であるhelloが格納されました。

プロパティbはゲッタなので、
`sample.b`で値を受け取っています。

```swift

struct Sample {
    let a: String
    var b: String {
        return "a = \(a)"
    }
    
    init(a: String) {
        self.a = a
    }
}

let sample = Sample(a: "hello")
print(sample.b)

実行結果
a = hello
```

##失敗可能なイニシャライザ

イニシャライザは本来全てのプロパティを正しい型の値で初期化する役割を果たしていますが、
イニシャライザの引数によってはプロパティを初期化できないケースが出てきます。

**初期化に失敗する可能性があるイニシャライザは
失敗可能イニシャライザとして表現でき、結果をOptional\<Wrapped>型で返します。**

失敗可能イニシャライザは、initキーワードに？を加えて`init?(引数)`のように記述します。

初期化が失敗した場合はインスタンス化が行われないので、
プロパティを未初期化のままにすることが可能です。

失敗可能イニシャライザの例を書いてみました。

下に流れを順に書いています。

```swift

struct Student {
    let name: String
    let number: Int
    
    init?(info: [String: Any]) {
        guard let name = info["name"] as? String, let number = info["number"] as? Int else {
            return nil
        }
        
        self.name = name
        self.number = number
    }
}


let students: [[String: Any]] = [
    ["name": "Aihara", "number": 1],
    ["name": "Inoue", "number": 2],
    ["name": "Tanaka"],
    ["name": "Watanabe", "number": 40]
]

for student in students {
    if let item = Student(info: student) {
        print(item)
    } else {
        print("エラー：生徒情報\(student)からStudentを生成できませんでした。")
    }
}

実行結果
Student(name: "Aihara", number: 1)
Student(name: "Inoue", number: 2)
エラー：生徒情報["name": "Tanaka"]からStudentを生成できませんでした。
Student(name: "Watanabe", number: 40)
```
**二次元配列の定義**
`let students: [[String: Any]] = [・・・]`
[String: Any]型の辞書が格納されている二次元配列を定義しました。

**for文**
`for student in students {・・・}`
上で定義した二次元配列の中身の[String: Any]型の値をstudentに格納しfor文を回します。

**for文の中のif文**
`if let item = Student(info: student) {・・・}`
studentの中に入っている値を引数に指定し、構造体Studentをインスタンス化しています。
値がnilでない場合はitemに値を入れ、nilの場合はelse節に移動します。

**イニシャライザ**
`init?(info: [String: Any]) {・・・}`
インスタンス化される際に渡されている引数でイニシャライザを行います。

`guard let name = info["name"] as? String`
引数でもらった値は引数名infoからアクセスできます。

処理としては、`info["name"]`でキーをnameにしているので、
それに該当する値を`as? String`つまり、String型として取り出しています。

`let number ・・・`の方も同じ流れです。

両方とも値が存在した場合は、
`self.name = name`と`self.number = number`が実行されそれぞれ値が入ります。

しかし、どちらかの値が存在しなかった場合はguard-let文の中に移動し、
`return nil`でnilが返されます。

といった処理の流れになります。
少しややこしいですが理解できましたか・・・？

説明が下手でしたらすみません。

##初期化チェック

プロパティの初期化はコンパイラによってチェックされています。

一つでもプロパティが初期化されていない場合は、
**型の整合性が取れなくなってしまうのでコンパイルエラーになってしまいます。**

コンパイルエラーになる例は次のようなコードです。

エラーが起きる理由としては、
**Sample型のインスタンス化時にプロパティnameの値がセットされておらず
Samole型のインスタンスが持つべき値が全て揃っていないためエラーになります。**

```swift

struct Sample {
    var name: Int
    var message: String {
        return "hello \(name)"
    }
    
    init(name: String) {
        // コンパイルエラー
    }
}
```

エラー内容：`Return from initializer without initializing all stored properties`
和訳：保存されているすべてのプロパティを初期化せずに初期化子から戻る


イニシャライザ内で全てのプロパティに対して初期化処理を行えば、
型の整合性を保つことができるのでコンパイルエラーはなくなります。

```swift

struct Sample {
    var name: String
    var message: String {
        return "hello \(name)"
    }
    
    init(name: String) {
        self.name = ""
    }
}
```

また、プロパティの初期化は行っているつもりでも、
初期化せずにインスタンス化を終えてしまう条件が
**一つでも**存在する場合も残念ながらコンパイルできません。

```swift

struct Sample {
    var name: String
    var message: String {
        return "hello \(name)"
    }
    
    init(dictionary: [String: String]) {
        if let name = dictionary["name"] {
            self.name = name
        }
        // コンパイルエラー
    }
}
```

`if let name = dictionary["name"]`で、
値が存在すればプロパティnameに値を入れています。

しかし、値が存在しない場合の処理が何も書かれていないため、
プロパティnameには値が入らずに終了してしまします。

対処方法としては、**else節で初期化の処理を記述する**、**失敗可能イニシャライザにする**、
**デフォルト値でプロパティを埋める**などの選択指があります。

```swift

// else節に処理を記述する
struct ElseSample {
    var name: String
    var message: String {
        return "hello \(name)"
    }
    
    init(dictionary: [String: String]) {
        if let name = dictionary["name"] {
            self.name = name
        } else {
            self.name = "none"
        }
    }
}

// init?()を使い失敗可能イニシャライザを使用する
struct InitSample {
    var name: String
    var message: String {
        return "Hello \(name)"
    }
    
    init?(dictionary: [String: String]) {
        guard let name = dictionary["name"] else {
            return nil
        }
        self.name = name
    }
}

// デフォルト値でプロパティに値を入れる
struct DefaultSample {
    var name: String
    var message: String {
        return "Hello \(name)"
    }
    
    init(dictionary: [String: String]) {
        name = dictionary["name"] ?? "none"
    }
}
```

以上がイニシャライザの説明になります。

構造体を作る上でイニシャライザは大切な構成要素なので、
ぜひ覚えていろいろな場面で使ってみてください！

また、他の構成要素についても記事にしているのでぜひご覧ください！

・[【Swift】型の構成要素〜型の基本〜](https://qiita.com/onishiApp/items/759c96bdbaebb2303a94)
・[【Swift】型の構成要素〜プロパティ前編〜](https://qiita.com/onishiApp/items/db19c212ce6eeb42ef9e)
・[【Swift】型の構成要素〜メソッド〜](https://qiita.com/onishiApp/items/dfe5be6ed494ceae1da0)
・[【Swift】型の構成要素〜サブスクリプト〜](https://qiita.com/onishiApp/items/281daff3d84c13bdd586)
・[【Swift】型の構成要素〜エクステンション〜](https://qiita.com/onishiApp/items/c7e407fc6aa6e922a7da)
・[【Swift】型の構成要素〜型のネスト〜](https://qiita.com/onishiApp/items/040019f1a00826b5c9e5)

最後までご覧いただきありがとうございました。