Swiftの格納プロパティ(Stored Properties)

SwiftのStored Properties

格納プロパティ(Stored Properties)はクラスと構造体内部で使えるプロパティです。列挙型では使えません。端的に言うと、格納プロパティは変数や定数のことです。

ここでは、格納プロパティとは何か?、構造体を使った格納プロパティの実例、またlazyプロパティ(lazy stored properties)とは何か?を説明します。

格納プロパティ(stored properties)は2種類 | 変数と定数

変数や定数格納プロパティ(Stored Properties)に該当します。それらの変数などは、クラス(または構造体)インスタンスの一部として「格納」されているので、“stored” propertyと呼ばれています。日本語訳は、直訳だと「格納(型)プロパティ」か「記憶(型)プロパティ」でしょうか?少し意味は偏りますが、「値プロパティ」でも良いような気もします。ここでは格納プロパティと呼びます。

Swiftの格納プロパティは2種類あります。

  • 変数はvariable stored propertiesvarキーワードで指定)、
  • 定数はconstant stored propertiesletキーワードで指定)
  • と呼ばれています。以下、意訳ですが、変数プロパティと定数プロパティという呼び方を使います。実際は呼び方が違うだけで、使い方はこれまでの変数や定数と変わりません。

    格納プロパティの例 | バッテリー構造体

    クラスと構造体のページで作った、バッテリー構造体を使って格納プロパティを説明します。

    // バッテリー構造体
    struct Battery {
        let power: Double
        var useTime: Int
    }
    
    // 10Vの電源で、稼働時間は5分
    var someBattery = Battery(power: 10.0, useTime: 5)
    // useTimeは変数プロパティなので変更可能
    someBattery.useTime = 3
    // powerは定数プロパティ。変更できない
    someBattery.power = 11.0
    // コンパイルエラー: cannot assign to property: 'power' is a 'let' constant
    

    このバッテリー構造体では、定数プロパティpowerと、変数プロパティuseTimeを定義しています。インスタンスを生成する際に、それらのプロパティを初期化しています(構造体のmemberwise initializer)。変数プロパティは「変数」ですから、上記のように値を変更できます。しかし、定数プロパティは変更できません。

    構造体のインスタンスが定数の場合、格納プロパティは全て定数になる

    先程の例では、構造体のインスタンスを変数、var someBattery、で宣言しました。もし、インスタンスを定数にすると、どうなるでしょうか?

    // 定数インスタンス
    let anotherBattery = Battery(power: 14.0, useTime: 10)
    anotherBattery.useTime = 13
    // コンパイルエラー:cannot assign to property: 'anotherBattery' is a 'let' constant
    

    上記の例で分かるように、構造体インスタンスを定数にした場合、そのインスタンスが保有するプロパティは全て定数になります。したがって、構造体で変数プロパティとして定義しても、インスタンスが定数ならば、その変数プロパティは定数になります(上記の例ではuseTime)。これは、構造体が値型だからです。クラスの場合は、インスタンスが定数でも、プロパティを変更可能です(クラスは参照型)。

    Lazy Stored Properties

    Lazy Stored Propertiesとは、そのプロパティが最初に使用されるまで初期化されないようなプロパティです。なんだそりゃ?と思うかもしれませんので、早速実例を示します。ちなみに、lazyは「遅い」とか「ゆるやかな」という和訳があります(「怠惰な」とか「くつろいだ」という意味もありますが、ここでは遅い等の方が意味としては近いかと思います)。

    掃除機管理クラスでlazy stored propertiesの実例

    以前、掃除機クラスを作りましたので、(ちょっと強引ですが)掃除機の仕様を外部から取り込むためのクラスを作ってみます。中身はスカスカですが、必要な部分の雛形だけを示す例になってます。構造は公式マニュアルの例とほぼ一緒です。

    // Lazy stored propertiesの使用例
    class CleanerDataBase {
        // データベースのインポート・エクスポートが可能なクラス
        init() {
            print("データベース生成")
        }
    
        // エクスポート
        func export() {
        }
    }
    
    class CleanerDataManager {
        var file = ""
        lazy var dataBase = CleanerDataBase()
    }
    
    let manager = CleanerDataManager()
    print("1")
    // CleanerDataBaseインスタンスはまだ生成されていない
    
    manager.file = "説明書"
    print("2")
    // CleanerDataBaseインスタンスはまだ生成されていない
    
    manager.dataBase.export()
    print("3")
    // CleanerDataBaseインスタンス生成
    
    //出力結果
    //1
    //2
    //データベース生成
    //3
    

    ここでは2つクラスを用意しました。CleanerDataBaseは、データベースのインポートとエクスポートを行う(予定)のクラスです。今は、export()メソッドで、エクスポートの形だけを用意して中身はカラです。CleanerDataManagerは、掃除機の仕様を管理するためのクラスです。プロパティとしては、仕様書のファイルfileと、その詳細を取り扱うCleanerDataBaseインスタンスdataBaseです。今、dataBaselazy指定されています。したがって、このプロパティはmanagerインスタンスから呼び出されるまで生成されません。

    上記の例では、先ずCleanerDataManagerインスタンスを生成しています。この時点ではdataBaseインスタンスは生成されていません。さらに、fileプロパティに文字列"説明書"を代入していますが、この時点でもまだdataBaseインスタンスは生成されません。最後に、manager.dataBase.export()で、CleanerDataBaseのメソッドを呼び出していますが、ここでようやくdataBaseインスタンスが作られます。

    ちなみに、今CleanerDataManagerのインスタンスmanagerは定数ですが、その変数プロパティfileを変更しています。繰り返しになりますが、クラスは参照型なので、定数インスタンスでもプロパティを変更することができます。

    lazyプロパティの定義 | 変数でないとダメ(定数にはできない)

    lazyプロパティを定義する際は、lazyキーワードを変数定義の前に指定します。公式マニュアルにも注意書きがありますが、lazyプロパティには必ず変数を使います。上記の例のように、インスタンス自体の初期化が終わっても、lazyプロパティは生成すらされていません。定数にすると、インスタンス初期化の際に必ず何かしらの値を割り当てなければいけませんから、lazyプロパティを定数にすることはできません。

    試しにletを指定すると

    ....
        lazy let dataBase = CleanerDataBase()
    ....
    // コンパイルエラー:'lazy' cannot be used on a let
    

    怒られます。

    “NOTE

    You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.”

    抜粋:: Apple Inc. “The Swift Programming Language (Swift 2.2)”。 iBooks https://itun.es/jp/jEUH0.l

    lazyプロパティは、どういう時に使う?

    プロパティによっては、何か特定の状況でなければ必要でないものや、外部入力に依存するもの、また初期化等に何かしら大量の(複雑な)演算が必要なもの、などがあるかもしれません。そのようなプロパティを、インスタンスを作った時に毎回生成・初期化するのは、メモリ(と時間)の無駄になります。

    このような場合にlazyプロパティが役に立ちます。プロパティをlazy指定をすると、実際に使用するまで、そのプロパティは生成されません(メモリに割り当てられない)。したがって、上記のようにプロパティの生成を遅らせたり、そもそもプロパティ自体を生成せずに、クラス(や構造体)を破棄することも可能です。

    まとめ

    • 格納プロパティ(Stored Properties)は2種類、変数(var)と定数(let
    • 構造体の定数インスタンスでは、そのプロパティは全て定数になる
    • lazy指定で、プロパティの生成・初期化を遅らせることができる。