LoginSignup
72
41

More than 3 years have passed since last update.

【Swift】Lazyプロパティの使い方に気をつける(ドキュメントの確認は大事)

Last updated at Posted at 2020-01-25

普段使っているものの
実は注意しないと予期せぬ動作を起こしてしまう可能性があるものは
意外とたくさんあると感じています。

そういう時
Swiftのドキュメントを見てみると
注意書(NOTE)があったりして役に立ちます。

今回は一つの例として
lazyのついたプロパティについて見てみたいと思います。

Lazy Stored Property(遅延格納プロパティ)とは?

Swiftでは
lazyという修飾子を使うことで
最初に利用されるまで
初期化処理を走らなくさせることができます。

これを
Lazy Stored Property(遅延格納プロパティ)
と呼びます。

どういうときに使う?

下記のような場合に役に立ちます。

  • 初期値がインスタンス生成後の状態(他のプロパティなど)に依存している
  • セットアップが複雑で重く、使われるまでは生成する必要がない

例えば
下記のようなCalendarクラスを考えてみます。


class Calendar {
    private lazy var formatter: DateFormatter = {
        print("call")
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.locale = Locale.init(identifier: "ja_JP")
        return formatter
    }()

    var today: String {
        formatter.string(from: Date())
    }
}

let c = Calendar()

formatterの初期化処理は
Calendarインスタンスを生成した時点では
呼ばれませんが


c.today 

// call

とtodayプロパティ経由で
formatterに始めてアクセスした時点で
初期化処理が呼ばれるようになります。

そして一度初期化処理が走ると
値がメモリに保存されるため
再度処理は走りません。


let c = Calendar()
c.today
c.today

// call ※1回しか出力されない

実は複数回呼ばれる場合がある

しかし
これには例外があります。

Swiftのドキュメントによると

NOTE
If a property marked with the lazy modifier is accessed by multiple threads simultaneously 
and the property has not yet been initialized, 
there is no guarantee that the property will be initialized only once.

↓のLazy Stored Propertiesの項
https://docs.swift.org/swift-book/LanguageGuide/Properties.html

マルチスレッドで同時にアクセスされた場合に
まだ初期化処理が終わっていないと
1回しか呼ばれないことは保証されない。

とあります。

先ほどのソースを少し変更してみます。


class Calendar {
    private lazy var formatter: DateFormatter = {
        sleep(1)
        print("call")
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.locale = Locale.init(identifier: "ja_JP")
        return formatter
    }()

    var today: String {
        formatter.string(from: Date())
    }
}

初期化処理で少しsleepします。

そして2つのキューからアクセスするようにしてみると


let q1 = DispatchQueue(label: "1")
let q2 = DispatchQueue(label: "2")

let c = Calendar()
q1.async { print(c.today) }
q2.async { print(c.today) }

// call
// call
...

初期化処理が2回走ることがわかりました。

この中で
もし値の変更など
副作用を起こすような処理が入っていたら
2回値を変更してしまった結果
予期せぬ不具合を引き起こす可能性があるので
注意が必要です。

Stored Type Property(格納型プロパティ)は1回しか呼ばれない

ここで似たようなものとして
Stored Type Property(格納型プロパティ)
があります。

Stored Type Propertyはインスタンスに属しておらず
classstaticという修飾子がついた
型自体に属しているプロパティです。

そしてSwiftのドキュメントに
下記の記載があります。

Stored type properties are lazily initialized on their first access. 
They are guaranteed to be initialized only once, 
even when accessed by multiple threads simultaneously, 
and they do not need to be marked with the lazy modifier.

↓のType Propertiesの項
https://docs.swift.org/swift-book/LanguageGuide/Properties.html

つまり
Stored Type Propertyの場合には
マルチスレッドで同時にアクセスされたとしても
必ず1回だけ処理が走るということを保証しています。

先ほどのformatterプロパティを
Type Propertyに変えて
同じことをやってみます。


class Calendar {
    private static var formatter: DateFormatter = {
        sleep(1)
        print("call")
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.locale = Locale.init(identifier: "ja_JP")
        return formatter
    }()

    var today: String {
        Calendar.formatter.string(from: Date())
    }
}

let q1 = DispatchQueue(label: "1")
let q2 = DispatchQueue(label: "2")

let c = Calendar()
q1.async { print(c.today) }
q2.async { print(c.today) }

// call ※ 1回だけしか出力されない

1回しか初期化処理が走らないことがわかりました。

まとめ

lazyなプロパティは
不要なメモリの消費やパフォーマンスの低下を防ぐことができますが
使い方を誤ると
わかりづらい不具合を混ぜてしまう可能性があることがわかりました。

このlazyのように
Swiftのドキュメントには
実は気をつけなければいけないことなどの
情報が書いてありますので
一度全てに目を通しておくことや
定期的にチェックすることは大事ですね😃

もし何か間違いなどございましたら
ご指摘いただけますと嬉しいです🙇🏻‍♂️

72
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
41