# プロパティ

プロパティとは、クラス、構造体、列挙型などに紐づく値のことです。型や、型のインスタンスに紐づいた変数や定数のことをいいます。

# プロパティの定義方法

プロパティの定義方法は、変数や定数と同様に varlet で定義することができます。

例えば、クラスのプロパティを定義すると次のようになります。

class クラス名 {
  var プロパティ名: プロパティの型 =let プロパティ名: プロパティの型 =}

次の例では、クラスにプロパティを定義しています。プロパティの宣言と同時に初期値を代入しています。

class MyClass {
  var var1: Int = 1
  let var2 = "name"
}

let myClass = MyClass()

myClass.var1 // 1
myClass.var2 // name

let キーワードで定義した定数は、再代入はできません。代入しようとすると、コンパイラはエラーを出力します。

class MyClass {
  var var1: Int = 1
  let var2 = "name"
}

let myClass = MyClass()

myClass.var1 = 2

// 定数に再代入
myClass.var2 = "name 2"

/* 実行結果 */
// error: cannot assign to property: 'var2' is a 'let' constant
// myClass.var2 = "name 2"
// note: change 'let' to 'var' to make it mutable
//   let var2 = "name"

# プロパティの分類

Swiftのプロパティは種類によって異なる性質を持つので、次のように分類することができます。

# 定義される対象による分類

定義される対象による分類は次のようになります。

プロパティはインスタンスや型、クラス自身に定義することができます。それぞれのプロパティには定義する対象に合わせて、宣言時にキーワードを記述する必要があります。定義される対象によって性質は異なるので一つずつ見てみましょう。

# インスタンスプロパティ | Instance Properties

型のインスタンスに紐づくプロパティをインスタンスプロパティといいます。型のインスタンスに紐づくので、インスタンスごとに異なる値を持つことができます。

次の例では、構造体にインスタンスプロパティを定義しています。

struct Person {
  // インスタンスプロパティを定義
  var name = "Taro"
}


let person1 = Person()
var person2 = Person()

// 異なる name を設定する
person2.name = "Jiro"

print(person1.name) // Taro
print(person2.name) // Jiro

型のインスタンスに紐づくため、インスタンス化しないで使用すると、コンパイラはエラーを出力します。

struct Person {
  var name = "Taro"
}

// インスタンス化せずに参照するとエラーになる
print(Person.name)

/* 実行結果 */
// error: instance member 'name' cannot be used on type 'Person'
// print(Person.name)

# タイププロパティ | Type Properties

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

タイププロパティは、 static キーワードをプロパティにつけます。参照するときは、インスタンス化しないで直接型からアクセスできます。

struct Person {
  var name = "Taro"

  // static キーワードをプロパティにつける。
  static var kind = "Human"
}

// インスタンス化しないでアクセス可能
Person.kind //Human

タイププロパティは、インスタンスから使用することはできません。参照しようとするとコンパイラはエラーを出力します。

struct Person {
  var name = "Taro"
  static var kind = "Human"
}

let person1 = Person()
person1.kind

/* 実行結果 */
// error: static member 'kind' cannot be used on instance of type 'Person'
// person1.kind

タイププロパティは、宣言時に必ず初期値を持たせる必要があります。初期値がない場合は、コンパイラはエラーを出力します。

struct Person {
  var name = "Taro"

  // 初期値を設定していない
  static var kind: String
}

/* 実行結果 */
// error: 'static var' declaration requires an initializer expression or getter/setter specifier
//   static var kind: String

# クラスプロパティ | Class Properties

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

クラスプロパティは、 class キーワードをプロパティにつけて、ゲッタを定義します。ゲッタについては、コンピューテッドプロパティ で後述します。

class Person {
  var name = "Taro"

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

Person.kind //Human

# 値の保持による分類

値の保持による分類は次のようになります。

ストアドプロパティは値を保持し、コンピューテッドプロパティは値を保持しません。

# ストアドプロパティ | Stored Properties

ストアドプロパティは、値を保持するプロパティのことを言います。値を保持するものを全てストアドプロパティと呼びます。そのため、インスタンスプロパティやタイププロパティも値を持っていたらストアドプロパティのうちに入ります。

struct Person {
  // 値があるので、ストアドプロパティ
  var name = "Taro"

  // 値があるので、ストアドプロパティ
  static var kind = "Human"
}

# プロパティオブザーバー | Property Observers

ストアドプロパティは、値の変更を監視するプロパティオブザーバーという機能があります。変更前と変更後に処理を実行できる機能が備わっています。

# willSet

willSet は、値が変更される前に実行されます。willSetのスコープ内は、newValue という定数で、新しい値を参照することができます。

struct Person {
  var name = "Taro" {
    willSet {
      print("値が変更される前です: \(newValue)")
    }
  }
}

var person = Person()

// 値を変更する
person.name = "Jiro"

// 値が変更される前です: Jiro

# didSet

didSet は、値が変更された後に実行されます。

struct Person {
  var name = "Taro" {
    didSet {
      print("値が変更された後です: \(self.name)")
    }
  }
}

var person = Person()

// 値を変更する
person.name = "Jiro"

// 値が変更された後です: Jiro

# レイジーストアドプロパティ | Lazy Stored Properties

レイジーストアドプロパティは、アクセスされるまで初期化するのを遅延させるプロパティです。負荷の高いプロパティの初期化を遅らせて、パフォーマンスを上げたいときなどに使用されます。

レイジーストアドプロパティは、 lazy キーワードをプロパティの前に記述します。

次の例では、プロパティの読み込みを遅延させています。本来ならインスタンス化のときに初期化処理が呼び出されますが、lazy キーワードを述するとプロパティにアクセスした時点で呼び出されるようになります。

struct Person {
  lazy var name: String = {
    print("初期化が実行されました!")
    return "Taro"
  }()
}

// インスタンス化の時点では初期化処理は呼び出されない
var person1 = Person()
print("インスタンス化されました!")

// プロパティにアクセスしたら呼び出される
let name = person1.name

実行結果を見ると、インスタンス化された時点ではなく、プロパティにアクセスされたタイミングで初期化がされたことを確認できます。

インスタンス化されました!
初期化が実行されました!

また、レイジーストアドプロパティは、インスタンス化のあとに初期化されるので、初期化時に他のプロパティやメソッドを使うことができます。

struct Person {
  var age = 20

  lazy var name: String = {
    print("age: \(self.age)")
    return "Taro"
  }()
}

var person1 = Person()
let name = person1.name

// 実行結果
// age: 20

レイジーストアドプロパティに、let キーワードで定数を宣言することはできません。宣言しようとすると、コンパイラはエラーを出力します。

struct Person {
  // 定数にする
  lazy let name: String = {
    print("age: \(self.age)")
    return "Taro"
  }()
}

var person1 = Person()
let name = person1.name

/* 実行結果 */
// error: 'lazy' cannot be used on a let
//   lazy let name: String = {
//   ^~~~~

# コンピューテッドプロパティ | Computed Properties

コンピューテッドプロパティは、ストアドプロパティと違い、値を保持せずに算出した値を返すプロパティです。コンピューテッドプロパティは、アクセスごとに値を計算し直して返します。

コンピューテッドプロパティの宣言方法は、プロパティ名のあとに {} を記述し、get キーワードでゲッタを、set キーワードでセッタを定義します。

struct DrinkServer {
  var age = 10

  var drink: String {
    // ゲッタを定義
    get {
      if age >= 20 {
        return "Beer"
      }

      return "Orange Juice"
    }

    // セッタを定義
    set {
      print("setの値です: \(newValue)")
    }
  }
}

var drinkServer = DrinkServer()
let drink = drinkServer.drink // Orange Juice

drinkServer.age = 30
let drink2 = drinkServer.drink // Beer

drinkServer.drink = "Whisky"

/* 実行結果 */
// setの値です: Whisky

# 省略

コンピューテッドプロパティのゲッタは必須ですが、セッタは任意です。セッタを書かない場合は、get キーワードと {} を省略して書くことができます。

struct DrinkServer {
  var age = 10

  var drink: String {
    if age >= 20 {
      return "Beer"
    }

    return "Orange Juice"
  }
}

また、暗黙のreturn がコンピューテッドプロパティでも使用することができます。内部の処理が値の返却しかない場合はreturnを省略して書くことができます。

struct DrinkServer {
  var age = 10

  var drink: String {
    // return を省略して書く
    "Orange Juice"
  }
}

# プロパティラッパー | Property Wrappers

プロパティラッパー (Property Wrappers)は、Swift 5.1 から追加された機能です。再利用可能なプロパティをまとめて定義して、複数のクラスや構造体、列挙型で使用することができます。

プロパティラッパーを使用するには、@propertyWrapper キーワードを記述し、wrappedValue プロパティを定義します。

@propertyWrapper
struct 構造体名 {
  var wrappedValue: 型 = 値
}

次の例では、20以下の値を返すプロパティラッパーを定義しています。

@propertyWrapper
struct TwentyOrLess {
  private var number = 0
  var wrappedValue: Int {
    get { return number }
    set { number = min(newValue, 20) }
  }
}

wrappedValue は必須なので記述しないとコンパイラはエラーを出力します。

@propertyWrapper
struct TwentyOrLess {
  private var number = 0
}

/* 実行結果 */
// Property wrapper type 'TwentyOrLess' does not contain a non-static property named 'wrappedValue'

では、このプロパティラッパー TwentyOrLess を、他の構造体にラップしてみましょう。

@TwentyOrLess をプロパティの前に記述します。

@propertyWrapper
struct TwentyOrLess {
  private var number = 0
  var wrappedValue: Int {
    get { return number }
    set { number = min(newValue, 20) }
  }
}

// プロパティの前にプロパティラッパーを記述
struct StructA {
  @TwentyOrLess var numberA: Int
}

// プロパティの前にプロパティラッパーを記述
struct StructB {
  @TwentyOrLess var numberB: Int
}

var structA = StructA()
var structB = StructB()

structA.numberA // 0
structA.numberA = 10
structA.numberA // 10
structA.numberA = 30
structA.numberA // 20

structB.numberB // 0
structB.numberB = 10
structB.numberB // 10
structB.numberB = 30
structB.numberB // 20

通常の実装だと、複数の構造体に同じプロパティを定義しなければなりませんが、プロパティラッパーを使うことで、処理を共通化して、シンプルに記述することができました。