# イニシャライザ

イニシャライザとは、クラス、構造体、列挙型などでインスタンスを初期化させることをいいます。Swiftでは、すべてのプロパティはインスタンス化がされるまでに値が代入されていなければなりません。そのため、プロパティの宣言時に値を持たないプロパティは、イニシャライザで初期化をする必要があります。

# イニシャライザの定義方法

イニシャライザは、init キーワードで定義することができます。メソッド と似ていますが、func キーワードは必要ありません。

struct MyStruct {
  init(引数: 引数の型) {
    // 処理の記述
  }
}

次の例では、構造体にイニシャライザを定義しています。引数にインスタンス化時の値を受け取り、プロパティに代入しています。

struct Person {
  let name: String
  let age: Int

  // 引数の値をプロパティに代入
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

// インスタンス化
let person1 = Person(name: "Taro", age: 20)

person1.name // Taro
person1.age  // 20

# イニシャライザの引数

引数に対しては、関数と同様に、外部引数名が利用できます。

次の例では、イニシャライザの第2引数を外部引数にしています。名前は任意ですが、_ にすると呼び出し元では引数名を省略して書くことができます。

struct Person {
  let name: String
  let age: Int

  // name に対して myName を指定する
  // age に対しては省略させる
  init(myName name: String, _ age: Int) {
    self.name = name
    self.age = age
  }
}

// myNameを指定すし、ageは省略する
let person1 = Person(myName: "Taro", 20)

person1.name // Taro
person1.age  // 20

# イニシャライザ内でメソッドを呼び出す

イニシャライザ内では、メソッドを呼ぶことができます。

次の例では、イニシャライザ内で sayName メソッドを呼び出しています。

struct Person {
  let name: String
  let age: Int

  init(name: String, age: Int) {
    self.name = name
    self.age = age

    // メソッドを実行
    sayName()
  }

  func sayName() {
    print(self.name)
  }
}

let person1 = Person(name: "Taro", age: 20) // Taro

person1.name // Taro
person1.age  // 20

メソッドを呼ぶ前には、全てのプロパティの初期化が終わっている必要があります。そのため、プロパティの初期化が終わっていない途中でメソッドを呼び出すと、コンパイラはエラーを出力します。

struct Person {
  let name: String
  let age: Int

  init(name: String, age: Int) {
    self.name = name

    // 初期化が終わってないのに、呼び出している
    sayName()

    self.age = age
  }

  func sayName() {
    print(self.name)
  }
}

let person1 = Person(name: "Taro", age: 20) // Taro

// error: 'self' used before all stored properties are initialized
// sayName()
    
// note: 'self.age' not initialized
// let age: Int

# イニシャライザの必要性

型のインスタンス化の前にイニシャライザをする理由は、イニシャライザでプロパティの値を全て満たさないと、型の整合性がとれなくなるためです。Swiftは、コンパイラの静的な型チェックにより安全性を確保しています。値がない状態で、インスタンス化がされてしまうと、意図せぬ挙動を招く恐れがあります。

例えば、本来は非Optional型のプロパティに対して値を代入しないで、nilが入ってしまったとします。本来はnilを許容しないのにnilが入ってしまうと、型とプロパティの値で不整合が生じます。

struct Person {
  let name: String

  init(name: String) {}
}

let person1 = Person(name: "Taro")

/* 実行結果 */
// error: return from initializer without initializing all stored properties
// note: 'self.name' not initialized
// let name: String

他の言語では、強制的にnullや0が入るパターンがありますが、Swiftは強力な型チェックにより、より安全にプログラムを実行できるようになっています。

このような仕様から、イニシャライザ内でプロパティに初期化がされないケースがあると、コンパイラが検知してエラーを出力します。

次の例では、shouldSetName がfalse判定されたときにプロパティに値がセットされない可能性があります。

struct Person {
  let name: String

  init(name: String, shouldSetName: Bool) {
    // false のときは self.name がセットされない
    if shouldSetName {
      self.name = name
    }
  }
}

let person1 = Person(name: "Taro", shouldSetName: true)

/* 実行結果 */
// error: return from initializer without initializing all stored properties
// note: 'self.name' not initialized
// let name: String

このようにコンパイラ時に値の不整合が起きない仕組みを提供することで、プログラムの安全性を保証しています。

# 失敗可能イニシャライザ | Failable Initilizer

失敗可能イニシャライザは、初期化時に値のセットができない可能性のある場合に使うイニシャライザです。イニシャライザは値の整合性を保つためにプロパティを初期化します。しかし、渡される引数によっては初期化が成功しないケースがあります。その場合に、失敗可能イニシャライザを使って表現することができます。初期化に失敗した場合は、nilを返して、インスタンスの生成をスキップします。

失敗可能イニシャライザは、int? キーワードを使用します。

次の例では、構造体に対して失敗可能イニシャライザを定義しています。1つ目のインスタンス化のときは、引数が渡されて初期化に成功します。しかし、2つ目のインスタンスは引数の値が空なので初期化に失敗してnilが返されます。

struct Person {
    let name: String
    init?(_ name: String) {
        // 空だったら、インスタンス化しないで nil を返す
        if name.isEmpty { return nil }
        self.name = name
    }
}

// インスタンス化に成功する
let person1 = Person("Taro") // Optional(main.Person(name: "Taro"))

// 引数が空文字なので、インスタンス化に失敗する
let person2 = Person("") // nil

nilの可能性があるので、戻り値はOptional型になります。

# デフォルトイニシャライザ | Default Initializer

デフォルトイニシャライザは、プロパティの初期化が不要な場合に、暗黙的に定義されます。

次の例では、プロパティ宣言時に値を代入しています。そのため、暗黙的にイニシャライザが実装され、インスタンス化がされます。

struct Person {
  let name: String = "Taro"
  let age: Int = 20
  let sex: String = "male"
}

let person1 = Person()

person1.name // Taro
person1.age  // 20
person1.sex  // male