【Swift】型の種類〜クラス後編〜

この記事は、[【Swift】型の種類〜クラス前編〜](https://qiita.com/onishiApp/items/a34d7408a4b123d9d11f)の続きになります。
前編をご覧になっていない方は、もしよかったらご覧ください。

では、前編の続きから説明していきます。

##クラスに紐付く要素

クラスのインスタンスではなく、クラス自身に紐付く要素として、
**クラスプロパティ**と**クラスメソッド**が存在します。

似たような性質で、型のインスタンスではなく型自身に紐付く
スタティックプロパティとスタティックメソッドが存在します。

両者の違いとしては、
**スタティックプロパティとスタティックメソッドはオーバーライドできないのに対し、
クラスプロパティとクラスメソッドはオーバーライドすることができます。**

###クラスプロパティ

クラスプロパティは、クラスのインスタンスではなく
クラス自身に紐付くプロパティになります。

インスタンスに依存しない値を扱う場合に利用します。

クラスプロパティを定義するためには、
プロパティの宣言時に先頭に`classキーワード`を追加します。

また、クラスプロパティにアクセスするためには、
`型名.クラスプロパティ名`のように記述しアクセスします。

次のサンプルコードにおいて、
classNameクラスプロパティは、クラス名を返すプロパティです。

クラス名は全てのインスタンス間で同じなので、
クラスプロパティとして定義することが適切だと思います。

```swift

class A {
    class var className: String {
        return "A"
    }
}

class B: A {
    override class var className: String {
        return "B"
    }
}

A.className   // A
B.className   // B
```

###クラスメソッド

クラスメソッドは、クラスのインスタンスではなくクラス自身に紐付くメソッドになります。
インスタンスに依存しない処理を定義する際に利用します。

クラスメソッドを定義するには、
メソッドの定義の先頭に`classキーワード`を追加するだけです。

また、クラスメソッドを呼び出す際は
`型名.クラスメソッド名`となります。

次のサンプルコードの`inheritance()`クラスメソッドは、
クラスの継承関係を返すクラスメソッドになります。

サブクラスでは、スーパークラスの値に自身のクラス名を追加しています。

```swift

class A {
    class func inheritance() -> String {
        return "A"
    }
}

class B: A {
    override class func inheritance() -> String {
        return super.inheritance() + "<-B"
    }
}

class C: B {
    override class func inheritance() -> String {
        return super.inheritance() + "<-C"
    }
}

A.inheritance()   // A
B.inheritance()   // A<-B
C.inheritance()   // A<-B<-C
```

スタティックプロパティやスタティックメソッドと似た機能なので、
特に難しいことはやっていないと思います。

難しく感じた方はスタティックプロパティや
スタティックメソッドについて説明した[コチラ](https://qiita.com/onishiApp/items/db19c212ce6eeb42ef9e)の記事をご覧ください。

###スタティックとクラスの使い分け

スタティックプロパティとスタティックメソッドは
クラスに対しても使用することができます。

つまり、クラスに関して、**インスタンスではなく型自身に紐付く要素を定義する場合は、
スタティックプロパティ、スタティックメソッドと
クラスプロパティ、クラスメソッドの両方を選択することができます。**

先ほども記載した通り、
スタティックメソッドとスタティックプロパティはオーバーライドすることができません。
それに対し、クラスプロパティとクラスメソッドはオーバーライドすることができます。

**なので、どちらを選択するかは、
その値がサブクラスで変更される可能性があるかどうかで決まります。**

##イニシャライザの種類と初期化のプロセス

イニシャライザの役割は、**型のインスタンス化の完了までに全てのプロパティを初期化し、
型の整合性を保つこと**であることは別の記事で紹介しました。

ただの型のイニシャライザだとそこまでややこしくないのですが、
クラスには**継承関係**というものが存在しているので、
**様々な階層で定義されたプロパティが初期化されることを保証する必要があります。**

それを実現するためには、３つのルールがあるのですが、
その前にイニシャライザの種類について紹介したいと思います。

イニシャライザは、**指定イニシャライザ**と
**コンビニエンスイニシャライザ**の２種類に分類することができます。

###指定イニシャライザ

指定イニシャライザはクラスの主となるイニシャライザです。
指定イニシャライザの中では、**全てのストアドプロパティが初期化されている必要があります。**

指定イニシャライザは、今までに出てきたイニシャライザと同様に定義します。

```swift

class Sample {
    let a: String
    let b: String
    
    init(a: String, b: String) {
        self.a = a
        self.b = b
    }
}
```

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

コンビニエンスイニシャライザは、**指定イニシャライザを中継するイニシャライザです。**
(カタカナばかりですみません(笑))

コンビニエンスイニシャライザは内部で引数を組み立てて
指定イニシャライザを呼び出す必要があります。

定義方法はイニシャライザに`convenienceキーワード`を追加することで定義できます。

下記のサンプルコードでは、
プロパティが、a, b, c の３つからなるクラスを作成しました。

指定イニシャライザでは、a, b, c 全ての引数を取り、
それぞれのプロパティに代入して初期化を行っています。

`convenience init(a: String, b: String)`
しかし、コンビニエンスイニシャライザでは引数をa, b の２つのみ受け取っています。

`self.init(a: a, b: b, c: a + b)`
その後、自身のイニシャライザに値を渡しています。
aとbには渡された引数をそのまま渡していますが、c に対しては、a + b の値を渡しています。

```swift

class Sample {
    let a: String
    let b: String
    let c: String
    
    init(a: String, b: String, c: String) {
        self.a = a
        self.b = b
        self.c = c
    }
    
    convenience init(a: String, b: String) {
        self.init(a: a, b: b, c: a + b)
    }
    
    func printSample() {
        print("a: \(a)\nb: \(b)\nc: \(c)")
    }
}

let sampleA = Sample(a: "123", b: "456", c: "789")
let sampleB = Sample(a: "123", b: "456")

sampleA.printSample()
print("----")
sampleB.printSample()

実行結果
a: 123
b: 456
c: 789
----
a: 123
b: 456
c: 123456
```
コンビニエンスイニシャライザは、
**上記のようにイニシャライザに渡す中継のイニシャライザとしての役割を持ちます。**

###３つのルール
型の整合性を保った初期化を実現するために、
クラスのイニシャライザには次の３つのルールがあります。

**・指定イニシャライザは、スーパークラスの指定イニシャライザを呼ぶ
・コンビニエンスイニシャライザは、同一クラスのイニシャライザを呼ぶ
・コンビニエンスイニシャライザは、最終的に指定イニシャライザを呼ぶ**

このルールを満たしている場合は、
継承関係にある全てのクラスの指定イニシャライザが必ず実行され、
各クラスで定義されたプロパティが初期化されていることを保証します。

**逆に、一つでも満たされていないルールがある場合、
型の整合性が保てないためコンパイルエラーになります。**

少しでも型の整合性を保証し、
よりエラーの少ない環境を作ってくれるSwiftに感謝ですね・・・。

###デフォルトイニシャライザ
プロパティが存在しない場合や、全てのプロパティが初期化されている場合は、
指定イニシャライザ内で初期化する必要がありません。

その場合は、暗黙的にデフォルトの指定イニシャライザが定義されます。

```swift

class A {
    let a = 10
    let b = 10
    
    // 以下と同様のイニシャライザが自動的に定義される
    // init() {}
}
```

一つでも初期化がされていないプロパティが存在する場合は、
指定イニシャライザ内で初期化する必要が出てきます。

その場合は、明示的に指定イニシャライザを定義する必要があります。

##クラスのメモリ管理

Swiftはクラスのメモリ管理にARCという方式を採用しています。

ARCとは、**Automatic Reference Counting**の略になります。
直訳で**自動参照カウント**という訳になるのですが、そのままの名前通りの方式です。

ARCでは、クラスのインスタンスを生成するたびに、
そのインスタンスのためのメモリ領域を自動的に確保します。

つまり、インスタンスが多ければ多いほどメモリ使用量は大きくなります。

なので、**不要になったインスタンスは自動的に解放し、
メモリ領域を少しでも減らそう！という方式になります。**

では、どの時点で不要になったインスタンスなのかを判断するのかというと、
ここで自動参照カウントが力を発揮します。

**ARCでは、使用中の(必要な)インスタンスが解放されるのを防ぐため、
プロパティ、定数、変数からそれぞれのクラスのインスタンスへの参照が
いくつ存在しているのかを自動的にカウントしています。**


このカウントが０になった時、
**そのインスタンスはどこからも参照されていないことになりますのでメモリが解放されます。**

このカウントのことを参照カウントと呼びます。

###デイニシャライザ

ARCによってインスタンスが破棄されるタイミングでは、
クラスのデイニシャライザが実行されます。

デイニシャライザはイニシャライザの逆で、
**クリーンアップなどの終了処理を行うものになります。**

定義方法は次のようになります。

```swift

class クラス名 {
   deinit {
      クリーンアップなどの終了処理
   }
}
```

以上でクラスの説明を終了したいと思います。
前編・後編と長くなってしまいましたが最後までご覧いただきありがとうございました。

クラスの継承やイニシャライザを上手く使うことができれば、
コードの記述量も減り可読性・安全性の高いコードをかけるようになると思います。

どんどんクラスについての知識を深めていきましょうー！

他にも型の種類について記載している記事がありますのでぜひご覧ください！

・[【Swift】型の種類〜基礎知識〜](https://qiita.com/onishiApp/items/173a1bcf7d75e3ba095f)
・[【Swift】型の種類〜構造体〜](https://qiita.com/onishiApp/items/c78d34f18383cd241d71)
・[【Swift】型の種類〜列挙型〜](https://qiita.com/onishiApp/items/1650c4e581fc9c8d3255)

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