LoginSignup
0
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-17

この記事は、【Swift】型の種類〜クラス前編〜の続きになります。
前編をご覧になっていない方は、もしよかったらご覧ください。

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

クラスに紐付く要素

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

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

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

クラスプロパティ

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

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

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

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

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

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


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()クラスメソッドは、
クラスの継承関係を返すクラスメソッドになります。

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


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

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

難しく感じた方はスタティックプロパティや
スタティックメソッドについて説明したコチラの記事をご覧ください。

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

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

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

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

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

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

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

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

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

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

指定イニシャライザ

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

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


class Sample {
    let a: String
    let b: String

    init(a: String, b: String) {
        self.a = a
        self.b = b
    }
}

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

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

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

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

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

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

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

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


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

コンビニエンスイニシャライザは、
上記のようにイニシャライザに渡す中継のイニシャライザとしての役割を持ちます。

3つのルール

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

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

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

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

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

デフォルトイニシャライザ

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

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


class A {
    let a = 10
    let b = 10

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

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

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

クラスのメモリ管理

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

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

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

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

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

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

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

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

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

デイニシャライザ

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

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

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


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

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

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

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

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

【Swift】型の種類〜基礎知識〜
【Swift】型の種類〜構造体〜
【Swift】型の種類〜列挙型〜

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

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2