Swiftの計算プロパティをC++のgetter感覚で読むときに気をつけること

Swiftの var area: Int { width * height } みたいな書き方、最初は少し不思議に見えます。

C++でいうgetter関数に近いですが、Swiftではプロパティとして扱います。

公式でも、プロパティにはstored propertyとcomputed propertyがあると説明されています。

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/

stored propertyとcomputed property

stored propertyは値を保持します。

struct Size {
    var width: Int
    var height: Int
}

computed propertyは値を保持せず、アクセス時に計算します。

struct Size {
    var width: Int
    var height: Int

    var area: Int {
        width * height
    }
}

area という保存領域があるわけではありません。

let size = Size(width: 12, height: 8)
print(size.area) // 96

C++で書くなら、だいたいこの感覚です。

struct Size {
    int width;
    int height;

    int area() const {
        return width * height;
    }
};

Swiftでは呼び出し側が size.area になるので、値のように読めます。

setも書ける

computed propertyには getset を書けます。

struct Temperature {
    var celsius: Double

    var fahrenheit: Double {
        get {
            celsius * 9 / 5 + 32
        }
        set {
            celsius = (newValue - 32) * 5 / 9
        }
    }
}

newValue はsetterに渡ってくる新しい値です。

var temp = Temperature(celsius: 0)
temp.fahrenheit = 212
print(temp.celsius) // 100

これはC++のsetterに近いです。

重い処理は置きすぎない

computed propertyは見た目が値なので、読む側は軽い処理だと思いがちです。

var summary: String {
    items.map(\.name).joined(separator: ", ")
}

このくらいならまだ読めます。

ただ、ネットワークアクセス、ファイル読み込み、重い検索などを入れると、呼び出し側からコストが見えません。

var remoteValue: String {
    // これはやめた方がいい
}

Swiftでも、重い処理や失敗する処理はメソッドにした方が自然です。

func loadRemoteValue() async throws -> String {
    ...
}

setを書けるからといって何でも書かない

setter付きcomputed propertyは便利ですが、状態変更が見えにくくなることがあります。

user.displayName = "new name"

これが単純代入なのか、複数フィールドを更新しているのか、外からは見えません。

値の変換や別表現として自然なものだけに絞ると読みやすいです。

まとめ

Swiftの計算プロパティは、C++のgetter/setterに近いですが、呼び出し側では値のように見えます。

  • stored propertyは値を保持する
  • computed propertyはアクセス時に計算する
  • 軽い派生値ならcomputed propertyでよい
  • 重い処理、失敗する処理、非同期処理はメソッドに寄せる

() が付かないぶん、呼び出し側からコストが見えにくい。この点だけ意識しておくと扱いやすいです。

関連記事

参考