iOS
Swift

【Swift】プロパティについてのまとめ

はじめに

Swiftについて基礎からおさらい中です。
プロパティひとつとっても様々な書き方やそれに応じた能力があってとっ散らかるので忘れる前にまとめてみました。

この記事でご登場いただくプロパティの皆さんは以下の方々です。

  • ストアドプロパティ
  • コンピューテッドプロパティ
  • インスタンスプロパティ
  • スタティックプロパティ
  • クラスプロパティ

なお、用語はSwift実践入門を参考にしています。

プロパティとは

プロパティとは型(クラス、構造体、列挙型)を構成する要素の一つで、型もしくは型のインスタンスに紐付いた値を指します。
ちなみに型を構成する他の要素としてはメソッド、イニシャライザ、サブスクリプト、ネスト型があります。

struct Person {
    // これがプロパティ
    var name = "Conan"

    // これはメソッド
    func greet() {
        print("I'm \(self.name)")
    }
}

後述していきますが上記のプロパティは再代入可能プロパティであり、ストアドプロパティであり、インスタンスプロパティです。

分類

プロパティは宣言の際に付与するキーワードによって異なる性質を持つことができ、以下のように分類できます。

  1. 再代入の可否による分類
  2. 値の保持有無による分類
  3. 紐付く対象による分類

1.再代入の可否による分類

定義に使うキーワードで再代入の可否が決まります。キーワードは次の2パターンです。

  • varキーワード
  • letキーワード

varキーワード

varキーワードを使って定義したプロパティには、値の再代入が可能です。
はじめに代入した値と異なる値をプロパティに持たせることができます。

struct Person {
    var name = "Conan"

    mutating func greet() {
        self.name = "Shinichi"  // 再代入が可能
        print("I'm \(self.name)")  // I'm Shinichi
    }
}

※greet関数の頭についているmutatingですが、自身の値を変更する場合になるキーワードです。

varキーワードを使っておきながら再代入しない場合は警告が表示されます。

Variable 'xxx' was never mutated; consider changing to 'let' constant

再代入しない場合は下記のletを使いましょう。

letキーワード

letキーワードを使って定義したプロパティには、値の再代入が不可能です。
はじめに代入した値を上書きすることができません。

struct Person {
    let name = "Conan"

    mutating func greet() {
        self.name = "Shinichi"  // コンパイルエラー
        print("I'm \(self.name)")
    }
}

2.値の保持有無による分類

値を保持するかどうかにより、2パターンに分類できます。

  • ストアドプロパティ
  • コンピューテッドプロパティ

ストアドプロパティ

値を保持します。ここまで登場していたプロパティはすべてストアドプロパティでした。

struct Person {
    // ストアドプロパティ
    var name = "Conan"

    mutating func greet() {
        self.name = "Shinichi"  // 再代入が可能
        print("I'm \(self.name)")  // I'm Shinichi
    }
}

ストアドプロパティには、値の変更を監視するプロパティオブザーバーという機能が備わっています。
willSetキーワードで変更前に実行する処理、didSetキーワードで変更後に実行する処理が記載できます。willSetスコープ内では、変更される値にnewValueでアクセスできます。

struct Person {
    // ストアドプロパティ
    var name = "Conan" {
        willSet {
            // プロパティ変更前に実行する
            print("I'm \(self.name). I'll be \(newValue)")
        }
        didSet {
            // プロパティ変更後に実行する
            print("I became \(self.name)")
        }
    }

    func greet() {
        print("I'm \(self.name)")
    }
}

var person = Person()
person.greet()
person.name = "Shinichi"
person.greet()
実行結果
I'm Conan
I'm Conan. I'll be Shinichi
I became Shinichi
I'm Shinichi

変数の代入前後に処理が実行されていることがわかります。

コンピューテッドプロパティ

値を保持せず、算出した値を返します。
定義するにはプロパティ名のあとに{}を書き、その中にgetキーワードでゲッタを、setキーワードでセッタを記載します。setスコープ内では、設定される値にnewValueでアクセスできます。ゲッタの定義は必須ですがセッタは任意です。
他言語を学んだことがあると勘違いしてしまいますが、ここでのセッタはコンピューテッドプロパティ自身に値を設定するためのものではなく、他のストアドプロパティに値を設定するために使用するものです。

struct Person {
    // ストアドプロパティ
    var firstName = "Conan"
    // コンピューテッドプロパティ
    var lastName: String {
        get {
            // 値の返却
            if firstName == "Conan" {
                return "Edogawa"
            } else {
                return "Kudo"
            }
        }
        set {
            // 値の更新
            if newValue == "Edogawa" {
                firstName = "Conan"
            } else {
                firstName = "Shinichi"
            }
        }
    }

    func greet() {
        print("I'm \(self.firstName) \(self.lastName)")
    }
}

var person = Person()
person.greet()
person.firstName = "Shinichi"
person.greet()
person.lastName = "Edogawa"
person.greet()
実行結果
I'm Conan Edogawa
I'm Shinichi Kudo
I'm Conan Edogawa

firstNameの値に合わせてlastNameの返却値が変化していること、lastNameの設定に合わせてfirstNameに再代入が行われていることがわかります。

3.紐付く対象による分類

プロパティが紐付く対象により、3パターンに分類ができます。

  • インスタンスプロパティ
  • スタティックプロパティ
  • クラスプロパティ

インスタンスプロパティ

型のインスタンスに紐付くプロパティをインスタンスプロパティといいます。
ここまで登場していたプロパティはすべてインスタンスプロパティでした。

struct Person {
    // インスタンスプロパティ
    var name = "Conan"
}

型のインスタンスに紐付くため、インスタンス化しなければ使うことができません。

let person = Person()
// インスタンスプロパティ
person.name // OK
Person.name // NG

スタティックプロパティ

型自身に紐付くプロパティを、スタティックプロパティといいます。インスタンスが異なっても共通する値を設定する場合などに使用できます。
定義するには先頭にstaticキーワードをつけます。

struct Person {
    // インスタンスプロパティ
    var name = "Conan"
    // スタティックプロパティ
    static var author = "Gosho Aoyama"
}

インスタンスからプロパティを使うことはできません。

let person = Person()
// インスタンスプロパティ
person.name  // OK
Person.name  // NG

// スタティックプロパティ
person.author  // NG
Person.author  // OK

クラスプロパティ

クラスに紐付くプロパティを、クラスプロパティといいます。
スタティックプロパティ同様、インスタンスが異なっても共通する値を設定する場合などに使用できます。
定義するには先頭にclassキーワードをつけます。またストアドプロパティでなくコンピューテッドプロパティの書き方をする必要があります。

class Person {
    // インスタンスプロパティ
    var name = "Conan"
    // クラスプロパティ
    class var className: String {
        return "Person"  // コンピューテッドプロパティのgetキーワードを省略した書き方
    }
}

スタティックプロパティ同様、インスタンスからプロパティを使うことはできません。

let person = Person()
// インスタンスプロパティ
person.name  // OK
Person.name  // NG

// クラスプロパティ
person.className  // NG
Person.className  // OK

クラスプロパティは型に紐付くという点でスタティックプロパティと同じです。
両者の差は、サブクラスでオーバーライド可能か不可能かという点にあります。

クラスプロパティは、サブクラスでオーバーライドすることが可能になっています。

class Person {
    // クラスプロパティ
    class var className: String {
        return "Person"
    }

    // スタティックプロパティ
    static var auther: String {
        return "Gosho Aoyama"
    }
}

class Detective: Person {
    // クラスプロパティはオーバーライド可能
    override class var className: String {
        return "Detective"
    }

    // スタティックプロパティはオーバーライド不可
    // 以下 コンパイルエラー
    override var auther: String {
        return "Rampo Edogawa"
    }   
}

まとめ

基礎中の基礎であるプロパティでも改めておさらいしてみると様々なパターンがあることがわかりました。
Swift初学者やプロパティの基礎をおさえたい方々の参考となれば幸いです。