# クラスとは

Swiftのクラスは、構造体と同様にデータを管理することができます。構造体と似ていますが、次の点で異なります。

  • クラスは 参照型
  • クラスは 継承することができる

# クラスの定義方法

class クラスの名前 {}

次の例では、クラスの中にプロパティやメソッドを定義しています。インスタンスを生成した後には、それらの値を使用できることがわかります。

class Person {
  // ストアドプロパティを定義
  var name = "Taro"

  // コンピューテッドプロパティを定義
  var age: Int {
    return 10
  }

  // メソッドを定義
  func printName() {
    print(self.name)
  }
}

// インスタンスを生成する
let person1 = Person()

person1.name        // Taro
person1.age         // 10
person1.printName() // Taro

# クラスの特徴

Swiftのクラスは次のような特徴があります。

# 参照型

Swiftのクラスは、参照型です。値型の構造体と違い、複数の変数で一つのインスタンスの値を共有することができます。

class Person {
  var age: Int

  init(age: Int) {
    self.age = age
  }
}

let person1 = Person(age: 20)
let person2 = person1

// インスタンスの値を変更
person1.age = 30

// 代入された変数も値を共有しているので変更される
person2.age // 30

参照型の詳しい説明は、値型と参照型の章の 参照型 をご確認ください。

# 継承

Swiftのクラスは、他のクラスからメソッドやプロパティなどを継承することができます。クラスが他のクラスから継承する場合、継承するクラスのことを サブクラス 、継承されるクラスは、スーパークラス と呼びます。

# 継承の定義方法

Swiftで継承を定義するには、クラス名のあとにスーパークラスを記述します。

class クラスの名前: スーパークラスの名前 {}

次の例では、RedRectangle (サブクラス) が Rectangle (スーパークラス) を継承しています。

// スーパークラス
class Rectangle {
  var width: Int
  var height: Int

  init(width: Int, height: Int) {
    self.width = width
    self.height = height
  }
}

// Rectangle を継承する
class RedRectangle: Rectangle {}

let redRectangle1 = RedRectangle(width: 100, height: 100)

redRectangle1.width   // 100 
redRectangle1.height   // 100 

RedRectangle クラスには、ストアドプロパティやイニシャライザを定義していませんが、インスタンスを生成することができます。Rectangle を継承することによって、Rectangle のもつプロパティやイニシャライズを使うことができるためです。

# オーバーライド

サブクラスは、スーパークラスから継承するメソッドやプロパティなどを再定義することができます。これをオーバーライドといいます。

オーバーライドをするには、各プロパティやメソッドに、override キーワードを記述して宣言します。また、オーバーライドしたプロパティやメソッド内で super キーワードを使うとスーパークラスの実装を呼び出すことができます。

# プロパティのオーバーライド

プロパティのオーバーライドをするには、override キーワードに併せて、ゲッタとセッタを定義します。

class クラスの名前: スーパークラスの名前 {
  override var プロパティ: 型 {
    get {}

    set {}
  }
}

次の例では、Rectangle (スーパークラス)の width をオーバーライドしています。

class Rectangle {
  var width: Int
  var height: Int

  init(width: Int, height: Int) {
    self.width = width
    self.height = height
  }
}

class RedRectangle: Rectangle {
  // width をオーバーライドする
  override var width: Int {
    get {
      // superでスーパークラスの値を参照
      return super.width * 2
    }
    set {
      super.width = newValue
    }
  }
}

let redRectangle1 = RedRectangle(width: 100, height: 100)

redRectangle1.width // 200

# メソッドのオーバーライド

メソッドのオーバーライドも同様に、override キーワードを宣言します。

次の例では、RectangleprintWidth をオーバーライドしています。

class Rectangle {
  var width: Int
  var height: Int

  init(width: Int, height: Int) {
    self.width = width
    self.height = height
  }

  func printWidth() {
    print(self.width)
  }
}

class RedRectangle: Rectangle {
  // メソッドをオーバーライドする
  override func printWidth() {
    // スーパークラスの printWidth を呼び出す
    super.printWidth()
    print("サブクラスのprintWidth")
  }
}

let redRectangle1 = RedRectangle(width: 100, height: 100)

redRectangle1.printWidth()

// 実行結果
// 100
// サブクラスのprintWidth

# final

final キーワードを記述すると、サブクラスでオーバーライドされることを禁止することができます。

次の例では、printWidth がオーバーライドされるのを禁止しています。

class Rectangle {
  var width: Int
  var height: Int

  init(width: Int, height: Int) {
    self.width = width
    self.height = height
  }

  // printWidth のオーバーライドを禁止する
  final func printWidth() {
    print(self.width)
  }
}

サブクラスでオーバーライドをすると、コンパイラはエラーを出力します。

class RedRectangle: Rectangle {
  override func printWidth() {
    super.printWidth()
    print("サブクラスのprintWidth")
  }
}

/* 実行結果 */
// error: instance method overrides a 'final' instance method
//   override func printWidth() {
// note: overridden declaration is here
// final func printWidth() {

# クラスプロパティ

クラスプロパティは、クラスに紐づくプロパティです。タイププロパティのように、インスタンスが異なっても共通の値を保持するときに使用します。

クラスプロパティは、 class キーワードをプロパティに記述し、ゲッタを定義します。

class Person {
  var name = "Taro"

  // class キーワードをつけて、ゲッタを定義する
  class var kind: String {
    return "Human"
  }
}

Person.kind //Human

# クラスメソッド

クラスメソッドは、インスタンスではなく、クラス自身に紐づくメソッドです。インスタンスが異なっても共通の処理をしたいときに使用します。

クラスメソッドは、 class キーワードをメソッドに記述します。

class Person {
  class func hello() -> Void {
    print("Hello Person")
  }
}

Person.hello() // Hello Person

# タイププロパティとタイプメソッドとの使い分け

クラスプロパティとクラスメソッドは、タイププロパティタイプメソッド と用途が似ています。この二つの使い分けはプロパティとメソッドが サブクラスで継承されるべきか で判断することができます。

  • 継承先で変更されるべき なものは、クラスプロパティ/クラスメソッドを使う
  • 継承先で変更されないべき なものは、タイププロパティ/タイプメソッドを使う
class Parent {
  // 継承先で変更されるべきなので クラスプロパティ として定義している
  class var name: String {
    return "Parent"
  }

  // 継承先で変更されないべきなので タイププロパティ として定義している
  static var superClassName: String {
    return "Parent"
  }
}

class Child: Parent {
  // クラスプロパティをオーバーライドしている
  override class var name: String {
    return "Child"
  }
}

タイププロパティをオーバーライドしようすとすると、コンパイラはエラーを出力します。

class Parent {
  static var superClassName: String {
    return "Parent"
  }
}

class Child: Parent {
  // タイププロパティをオーバーライドしている
  override static var superClassName: String {
    return "Child"
  }
}

/* 実行結果 */
// error: cannot override static property
//   override static var superClassName: String {
// note: overridden declaration is here
//   static var superClassName: String {

# クラスのイニシャライザ

Swiftでは、型のインスタンス化が完了されるまでに、全てのプロパティの初期値を設定しなければなりません。これはクラスでも同様です。Swiftのクラスは構造体や列挙型とは違い、継承の特徴を持ちます。そのため、全ての階層のスーパークラスやサブクラスで適切に初期値がセットされているかを保証する必要があります。

その初期化を保証するために、いくつかのイニシャライザが提供されています。

# 指定イニシャライザ | Designated Initializer

指定イニシャライザは、必須のイニシャライザです。この中では、全てのストアドプロパティを初期化する必要があります。また、サブクラスでは、初期化後にスーパークラスの指定イニシャライザを呼び出されなければなりません。

指定イニシャライザは、init で定義します。

class Parent {
  var name: String

  // 指定イニシャライザを定義する
  init(name: String) {
    self.name = name
  }
}

class Child: Parent {
  // override でオーバーライドする
  override init(name: String) {
    print("Childのイニシャライズ")
    
    // スーパークラスの指定イニシャライザを呼び出されなければならない。
    super.init(name: name)
  }
}

サブクラスで、スーパークラスの指定イニシャライザを呼び出さないと、コンパイラはエラーを出力します。

class Parent {
  var name: String

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

class Child: Parent {
  override init(name: String) {
    print("Childのイニシャライズ")
    
    // スーパークラスの指定イニシャライザを呼ばない
    // super.init(name: name)
  }
}

/* 実行結果 */
// error: 'super.init' isn't called on all paths before returning from initializer

# コンビニエンスイニシャライザ | Convenience Initializer

コンビニエンスイニシャライザは、指定イニシャライザと違い、必須ではありません。コンビニエンスイニシャライザは引数を受け取り、内部で指定イニシャライザを呼び出します。指定イニシャライザの中継役の役割を果たします。

コンビニエンスイニシャライザを使うには、convenience キーワードを記述します。

class Person {
  var name: String

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

  // convenience キーワードを記述して、コンビニエンスイニシャライザを定義する
  convenience init(name: String, suffix: String) {
    // 指定イニシャライザを呼びだす
    self.init(name: "\(name) \(suffix)")
  }
}

let person1 = Person(name: "Taro", suffix: "さん")

person1.name // Taro さん

中継役であるコンビニエンスイニシャライザが、指定イニシャライザを呼び出すだけ前にプロパティをセットしようとすると、コンパイラはエラーを出力します。

class Person {
  var name: String

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

  convenience init(name: String, suffix: String) {
    // 指定イニシャライザが呼びされる前に、プロパティの変更をする
    self.name = "\(name) \(suffix)"
    self.init(name: "\(name) \(suffix)")
  }
}

let person1 = Person(name: "Taro", suffix: "さん")

/* 実行結果 */
// error: 'self' used before 'self.init' call or assignment to 'self'
//   self.name = "\(name) \(suffix)"

# 初期化の仕組み

指定イニシャライザとコンビニエンスイニシャライザの制約を守ると全てのスーパークラスやサブクラスで初期化が保証されます。

次の図のように常に関連し合っているクラスを呼び出すことで、最終的に全てのクラスの初期化を実行し、安全に型の整合性を保つことができるのです。

  • 継承された場合は、スーパークラスの指定イニシャライザを呼び出さなければならない
  • コンビニエンスイニシャライザは、同じクラスのイニシャライザを呼び出さなければならない
  • コンビニエンスイニシャライザは、最終的に同じクラスの指定イニシャライザを呼び出さなければならない

コマンドラインから実行する-1