Swiftの計算プロパティ(Computed Properties)

SwiftのComputed Properties

Computed Propertiesです。和訳すると「計算プロパティ」でしょうか。これは、値を保持するための箱ではなくて、型(クラスなど)が保持しているプロパティ等を使った、何かしらの計算結果を提供するプロパティです。機能としては、大雑把には関数(メソッド)と似ています。しかしながら、getterとsetterという機能が、一つの計算プロパティに組み込まれているので、関数とは一味違います。

Computed Propertiesって何?

Computed Propertiesは、値を保存するのではなく、何かしらの「計算(演算)」を提供するプロパティですので、“computed” propertyと呼ばれています。繰り返しになりますが、和訳を当てるとすれば「計算プロパティ」です。「計算型プロパティ」や「演算プロパティ」でも良いかもしれません。ここでは計算プロパティと呼びます。

計算プロパティの機能 | getter(必須)とsetter(任意)

具体的な計算プロパティの機能としては、

  • Getter: Stored propertiesや値を使った計算結果を返す機能
  • Setter: Stored propertiesや値から、別の計算結果を取り出したりセットする機能

があります。この後詳しく説明しますが、getterは計算結果を戻り値として返す機能、setterは新しい計算結果を既存のプロパティにセットする機能、になります。計算プロパティでは、getterは必須ですが、setterは任意です(定義しなくても構いません)。

計算プロパティの定義構文 | 直線(Line)構造体を使った具体例

では、実際に構造体を使って、計算プロパティの定義構文と、その使用例を見ていきます。ここでは、与えられた2点間を結ぶ直線を構造体で作ってみます(少し長いです)。

// 2次元上の点
struct Point {
    var x = 0.0
    var y = 0.0
}

// 直線構造体
struct Line {
    // 平均値を計算するメソッド
    func mean(_ values: Double...) -> Double? {
        guard !values.isEmpty else {
            return nil
        }
        
        var mean = 0.0
        for value in values {
            mean += value
        }
        return mean / Double(values.count)
    }
    
    var start: Point // 始点
    var stop: Point // 終点

    // 直線の中心
    var center: Point {
        get {
            let meanX = mean(start.x, stop.x)!
            let meanY = mean(start.y, stop.y)!
            return Point(x:meanX, y:meanY)
        }
        
        set(newCenter) {
            let shiftX = newCenter.x - mean(start.x, stop.x)!
            let shiftY = newCenter.y - mean(start.y, stop.y)!
            start.x += shiftX
            stop.x  += shiftX
            start.y += shiftY
            stop.y  += shiftY
        }
    }
    
    func show() {
        print("始点(\(start.x), \(start.y)):、終点:(\(stop.x), \(stop.y)) | 長さ:\(length)")
    }
}

var line = Line(start:Point(x:2,y:4), stop:Point(x:6,y:12))
line.show()
//始点(2.0, 4.0):、終点:(6.0, 12.0)、と表示

line.center = Point(x:8,y:1)
line.show()
//始点(6.0, -3.0):、終点:(10.0, 5.0)、と表示

少し長いので、順番に詳しく説明します。

2次元の1点を表す構造体Point

// 2次元上の点
struct Point {
    var x = 0.0
    var y = 0.0
}

これは自明です。変数プロパティ(variable stored properties)x, y0.0で初期化していますので、x, yの型はDoubleです(型推論)。公式マニュアルにもあるように、var x = 0.0, y = 0.0とも書けます。

直線構造体の始点と終点(変数プロパティ)

直線(line)を表す構造体は、変数プロパティを2つ持っています。

struct Line {
    ....
    var start: Point // 始点
    var stop: Point // 終点
    ....

    func show() {
        print("始点(\(start.x), \(start.y)):、終点:(\(stop.x), \(stop.y))")
    }
}

start(始点)とstop(終点)で、それぞれPoint型になっています。また、show()メソッドは、単に始点と終点の座標を表示するだけです。

蛇足ですが、構造体のプロパティはstart.xのように、dot syntax(インスタンス.プロパティ名)で取り出せます。

以下、プロパティのgetterとsetterを別々に詳しく見ていきます。

Getter計算プロパティで、直線の中心を計算

Getterの定義構文は、

// Getterの定義構文
var propertyName: type {
    get {
        return value
    }
}

のような形を取ります。戻り値であるvalueの型は、指定されたtypeでなければいけません。Getterはその名前の通り、必ず戻り値があります。

今、上記の例では、centerプロパティは直線の中心座標を返すgetterですから、

// 直線の中心
var center: Point {
    get {
        let meanX = mean(start.x, stop.x)!
        let meanY = mean(start.y, stop.y)!
        return Point(x:meanX, y:meanY)
    }
    ....
}

となります。メソッドmean(_:)は、入力したパラメータの平均値を返すメソッドです(後述します)。したがって、getterは、x方向の座標とy方向の座標の平均値をそれぞれ計算して、それらをPoint型の初期値にして返しています。

Setter計算プロパティで、中心を与えて直線を並進移動

次はsetterです。Setterは「set」という単語から分かるように、プロパティに新しい値を代入する機能です。定義構文はgetterとほぼ同じです。

// 直線の中心
var center: Point {
    ....
    set(newCenter) {
        let shiftX = newCenter.x - mean(start.x, stop.x)!
        let shiftY = newCenter.y - mean(start.y, stop.y)!
        start.x += shiftX
        stop.x  += shiftX
        start.y += shiftY
        stop.y  += shiftY
    }
}

ここでは、set(newCenter)となっています。これは、

setter内部で使う内部パラメータの名前をnewCenterにしなさい

という指定になります。newCenterの型は、もちろんPointです。このsetterで何をやっているかというと、新しい直線の中心(newCenter)を代入した場合、始点と終点を並進移動させる、という計算です。これは図示すると分かりやすいです。

[図解]Computed propertiesのsetter | 直線構造体

平均値を計算するメソッド

後は平均値を計算するメソッドです。

// 平均値を計算するメソッド
func mean(_ values: Double...) -> Double? {
    guard !values.isEmpty else {
        return nil
    }

    var mean = 0.0
    for value in values {
        mean += value
    }
    return mean / Double(values.count)
}

このメソッドはパラメータとして、可変パラメータ(variadic parameters)values: Double...を取ります。また、戻り値としてoptionalsDouble?を返します。Optionalsになっているのは、可変パラメータが空の場合にnilを返すためです。インプットが空の場合は、guard制御転送nilを返します。

平均値を計算する部分は簡単だと思います。最後の計算

    return mean / Double(values.count)

では、総和を個数values.countで割って平均値を計算しています。配列Arrayのプロパティcountは整数型なので、Doubleに型変換して割り算しています。

簡略Setter宣言(Shorthand Setter Declaration)

先程の例では、setterの内部パラメータとしてnewCenterを用意しました。しかし、デフォルトではnewValueという名前が割り当てられていますので、無理にパラメータ名を指定する必要はありません。したがって、setterをデフォルトのnewValueで書き換えると

// 簡略setter宣言
set {
    let shiftX = newValue.x - mean(start.x, stop.x)!
    let shiftY = newValue.y - mean(start.y, stop.y)!
    start.x += shiftX
    stop.x  += shiftX
    start.y += shiftY
    stop.y  += shiftY
}

という具合になります。デフォルトのnewValueを使用する場合は、setterのパラメータに名前を指定しなくても構いません。

読み出し専用計算プロパティ(Read-Only Computed Properties)

最初に述べましたが、計算プロパティでのsetterは必須ではなく任意です。Getterのみの計算プロパティを、読みだし専用計算プロパティ(Read-Only Computed Properties)と呼びます。先程の直線構造体に、直線の長さを計算するプロパティを追加します。

// 2点間の距離を返すプロパティ
import Darwin

struct Line {
    ....
    // 直線の長さ
    var length: Double {
        let dx = stop.x - start.x
        let dy = stop.y - start.y
        return sqrt(dx*dx + dy*dy)
    }
    ....
    func show() {
        print("始点(\(start.x), \(start.y)):、終点:(\(stop.x), \(stop.y)) | 長さ:\(length)")
    }
}

var line = Line(start:Point(x:2,y:4), stop:Point(x:6,y:12))
line.show()
//始点(2.0, 4.0):、終点:(6.0, 12.0) | 長さ:8.94427190999916

直線の長さは、三平方の定理を使って計算できます。最後に長さを計算する部分では、平方根(square root)を計算する組み込み関数sqrt(_:)を使用しています。この関数を呼び出すために、import Darwinをあらかじめ実行しておかないと、コンパイルエラーになります。

長さは、直線の2点を指定すれば自動的に計算できるので、読み出し専用になっています。もちろん、長さをsetterで指定できるようにして、その長さに応じて2点を移動させることも可能です。その場合、どこを中心に取るかで動かし方は変わります。

まとめ

  • 計算プロパティ(Computed Properties)は、値の保存ではなく、計算機能を提供するプロパティ
  • Getterは計算結果を戻り値として返す機能。Setterは新しい計算結果を既存のプロパティにセットする機能。
  • Getterは必須、Setterは任意。Getterのみの計算プロパティを読み出し専用計算プロパティ(Read-Only Computed Properties)と呼ぶ。
  • Setterのデフォルト内部定数はnewValue。独自の名前を付けることも可能