【Swift】構造体、クラスについて

# 目次
[はじめに](#はじめに)
[構造体](#構造体)
[クラス](#クラス)

# はじめに
Swiftには構造体、クラス、列挙型という3つの型の種類があり、それらにはプロパティやメソッドなどの共通した仕様があります。その一方で、三者それぞれに固有の使用も多く存在します。そこで今回は３つの型のうち、構造体とクラスの固有の使用について説明したいと思います。（列挙型については[こちら](https://qiita.com/Sena007/items/b0cb4b1d2f9c43d038cc)をご覧ください）

# 構造体
構造体は**値型**の一種であり、ストアドプロパティの組み合わせによって1つの値を表します。標準ライブラリで提供されている多くの方は構造体であり、数値型、String型、Bool型などの基本的な型はすべて構造体です。
### 定義方法
構造体の定義にはstructキーワードを使用します。
```swift
struct 構造体名 {
    構造体の定義
}
```
プロパティやメソッド、イニシャライザなどの型を構成する要素は構造体ですべて利用可能で、{ }内に定義できます。
```swift
struct Person {
    let name: String
    let age: Int
    let height: Double

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

    func printHeight() {
        print(height)
    }
}

let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.printHeight()  // 170.5
```
## ストアドプロパティの変更による値の変更
冒頭で述べたように構造体はストアドプロパティの組み合わせで1つの値を表す値型です。構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、**構造体が入っている変数や定数への再代入**を必要とします。

### 定数のストアドプロパティは変更できない
構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できません。
```swift
struct Person {
    var name: String

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

var person1 = Person(name: "Taro Suzuki")
person1.name = "Hanako Yamada"

let person2 = Person(name: "Taro Suzuki")
person2.name = "Hanako Yamada"  // 定数letに代入しているため再代入できず、コンパイルエラー
```
ちなみにクラスの場合は値型ではなく参照型であるため、エラーとなりません。
```swift
class Person {
    var name: String

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

var person1 = Person(name: "Taro Suzuki")
person1.name = "Hanako Yamada"

let person2 = Person(name: "Taro Suzuki")
person2.name = "Hanako Yamada"  // 定数letに代入していてもOK。
```
### メソッド内のストアドプロパティの変更にはmutaitingキーワードが必要
構造体のストアドプロパティの変更は再代入を必要とするため、ストアドプロパティの変更を含むメソッドにはmutatingキーワードが必要です。
```swift
struct Person {
    var name: String

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

    mutating func chageHanakoYamada() {
        name = "Hanako Yamada"
    }
}

var person1 = Person(name: "Taro Suzuki")
person1.chageHanakoYamada()
person1.name  // "Hanako Yamada"
```
構造体のメソッド内のストアドプロパティの変更はコンパイラによってチェックされ、ストアドプロパティの変更を含んでいるにもかかわらずmutatingキーワードが付いていない場合はコンパイルエラーとなります。
```swift
struct Person {
    var name: String

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

    func chageHanakoYamada() {
        name = "Hanako Yamada". // mutatingが付いていないのでコンパイルエラー
    }
}
```


## 構造体のイニシャライザ

### メンバーワイズイニシャライザ
型のインスタンスは初期化後にすべてのプロパティが初期化されている必要があります。そこで独自にイニシャライザを定義して初期化の処理を行うこともできますが、構造体では**自動的に定義されるメンバーワイズイニシャライザ(memberwise initializer)というイニシャライザを利用できます**。
メンバーワイズイニシャライザは、型が持っている各ストアドプロパティと同名の引数を取るイニシャライザです。
```swift
struct Person {
    let name: String
    let age: Int
    let height: Double

//  以下と同等のイニシャライザが自動的に定義される
//  init(name: String, age: Int, height: Double) {
//      self.name = name
//      self.age = age
//      self.height = height
//  }
}

let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.name  // "Taro Suzuki"
person1.age  // 28 
person1.height  // 170.5 
```
### デフォルトイニシャライザ
プロパティが存在しない場合や、すべてのプロパティが初期値を持っている場合、暗黙的にイニシャライザinit()が定義されます。
```swift
struct Person {
    let name: String = "Taro Suzuki"
    let age: Int = 28
    let height: Double = 170.5

    // 以下と同等のイニシャライザが自動的に定義される
    // init() {}
}

let person1 = Person()
```

# クラス
クラスは構造体と同様の構造を持つ型です。構造体との大きな違いは2つあり、一つは**参照型**であること、もう一つは**継承が可能であること**です。構造体や[列挙型](https://qiita.com/Sena007/items/b0cb4b1d2f9c43d038cc)と比べると、プロパティやメソッドの文法、初期化のフローなどに違いがありますが、これは継承を考慮する必要があるためです。Cocoaのほとんどの型はクラスとして定義されています。
### 定義方法
クラスの定義にはclassキーワードを使用します。
```swift
class クラス名 {
    クラスの定義
}
```
構造体と同様に、プロパティやメソッド、イニシャライザなどの型を構成する要素は構造体ですべて利用可能で、{ }内に定義できます。
```swift
class Person {
    let name: String
    let age: Int
    let height: Double

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

    func printHeight() {
        print(height)
    }
}

let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.printHeight()  // 170.5
```
## 継承
継承とは、新たなクラスを定義するときに、**他のクラスのプロパティ、メソッド、イニシャライザなどの型を再利用する仕組み**です。継承先のクラスでは継承元のクラスと共通する動作をあらためて定義する必要がなく、継承元のクラスとの差分のみを定義すれば済みます。
継承先のクラスは継承元のクラスの**サブクラス**、継承元のクラスは継承先のクラスの**スーパークラス**と言います。
Swiftでは、複数のクラスから継承する多重継承は禁止されています。

### 定義方法
クラスに継承関係を定義するには、クラス名の後に「：」とスーパークラス名を記述します。
```swift
class クラス名: スーパークラス名 {
    クラスの定義
}
```


### オーバーライド
スーパークラスで定義されているプロパティやメソッドなどの要素は、サブクラスで定義できます（スタティックプロパティはできません)。これを**オーバーライド**と言います。
オーバーライドを行うには、overrideキーワードを使用してスーパークラスで定義されている要素を再定義します。
```swift
class クラス名: スーパークラス名 {
    override func メソッド名(引数) -> 戻り値の型 {
        メソッド呼び出し時に実行される文
    }

    override var プロパティ名: 型名 {
        get {
            return文によって値を返す処理
            superキーワードでスーパークラスの実装を利用できる
        }

        set {
            値を更新する処理
            superキーワードでスーパークラスの実装を利用できる
        }
    }
}
```
オーバーライドしたプロパティやメソッドの中でsuperキーワードを使用することで、**スーパークラスの実装を呼び出すこともできます**。
次の例では、Userクラスで定義されているmessageプロパティとprintProfile()メソッドをRegisteredUserでオーバーライドしており、printProfile()メソッドでは、super.printProfile()によってスーパークラスの実装を呼び出しています。
```swift
class User {
    let id: Int

    var message: String { return "Hello." }

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

    func printProfile() {
        print("id: \(id)")
        print("message: \(message)")
    }
}

class RegisteredUser: User {
    let name: String

    override var message: String { return "Hello, my name is \(name)." }

    init(id: Int, name: String) {
        self.name = name
        super.init(id: id)
    }

    override func printProfile() {
        super.printProfile()
        print("name: \(name)")
    }
}

let user = User(id: 1)
user.printProfile()

print("-------------")

let registeredUser = RegisteredUser(id: 2, name: "Yusei Nishiyama")
registeredUser.printProfile()



// 以下、実行結果
id: 1
message: Hello.
-------------
id: 2
message: Hello, my name is Yusei Nishiyama.
name: Yusei Nishiyama
```
### finalキーワード
オーバーライド可能な要素の前にfinalキーワードを記述することで、その要素が**サブクラスでオーバーライドされることを禁止できます**。
次の例では、overridableMethod()メソッドはオーバーライドできますが、finalMethod()メソッドはfinalキーワードとともに定義されているため、オーバーライドしようとするとコンパイルエラーとなります。
```swift
class SuperClass {
    func overridableMethod() {}

    final func finalMethod() {}
}

class SubClass: SuperClass {
    override func overridableMethod() {}

    // オーバーライド不可能なためコンパイルエラー
    override func finalMethod() {}
}
```
またクラス自体にfinalキーワードを付与することで、そのクラスを継承したクラスを定義することを禁止できます。次の例では、InheritableClasクラスは継承できますが、FinalClassクラスはfinalキーワードとともに定義されているため、継承しようとするとコンパイルエラーとなります。
```swift
class InheritableClass {}

class ValidSubClass: InheritableClass {}

final class FinalClass {}

// 継承不可能なためコンパイルエラー
class InvalidSubClass: FinalClass {}
```
## クラスのイニシャライザ

### 指定イニシャライザ
指定イニシャライザ(designated initializer)はクラスの主となるイニシャライザで、このイニシャライザの中ですべてのストアドプロパティが初期化される必要があります。
```swift
class Mail {
    let from: String
    let to: String
    let title: String

    // 指定イニシャライザ
    init(from: String, to: String, title: String) {
        self.from = from
        self.to = to
        self.title = title
    }
}
```
### コンビニエンスイニシャライザ
コンビニエンスイニシャライザ(convenience initializer)は指定イニシャライザを中継するイニシャライザで、内部で引数を組み立てて指定イニシャライザを呼び出す必要があります。
コンビニエンスイニシャライザはconvenienceキーワードを追加することで定義できます。
```swift
class Mail {
    let from: String
    let to: String
    let title: String

    // 指定イニシャライザ
    init(from: String, to: String, title: String) {
        self.from = from
        self.to = to
        self.title = title
    }

    // コンビニエンスイニシャライザ
    convenience init(from: String, to: String) {
        self.init(from: from, to: to, title: "Hello, \(to).")
    }
}
```
### 必須イニシャライザ
イニシャライザにrequiredキーワードを記述することで、クラスを継承する際、サブクラスが必ずそのイニシャライザを実装することを示すことができます。
```swift
class User {
    let id: Int
    required init(id: Int) {
        self.id = id
    }
}

class RegisteredUser: User {
    required init(id: Int) {
        super.init(id: id)
    }
}
```

### デフォルトイニシャライザ
構造体と同様にプロパティが存在しない場合や、すべてのプロパティが初期値を持っている場合、暗黙的に指定イニシャライザinit()が定義されます。
```swift
class User {
    let id = 0
    let name = "Taro"

    // 以下と同等のイニシャライザが自動的に定義される
    // init() {}
}

let user = User()
```

# 参考
・Swift実践入門 (https://gihyo.jp/book/2020/978-4-297-11213-4)
・公式リファレンス (https://docs.swift.org/swift-book/)