読み込み中...

Swiftでの遅延格納プロパティの使い方!完全ガイド10選

Swiftプログラムの遅延格納プロパティのイメージ図 Swift
この記事は約16分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swift言語を学び、深めていく中で、遅延格納プロパティという特性に出会うことでしょう。

この記事では、Swiftの遅延格納プロパティに関する知識を基本から深堀りして、その使い方や注意点、応用例までを10のサンプルコードを交えて詳しく解説します。

Swiftの初心者から上級者まで、この記事が皆さんのプログラミングスキルの向上に貢献することを願っています。

●Swiftの遅延格納プロパティとは

遅延格納プロパティは、その名の通り、プロパティの初期化を遅らせることができる特性を持ったプロパティのことを指します。

Swiftではlazyキーワードを使用することで、この特性を活用することができます。

しかし、なぜそもそも遅延格納プロパティが必要なのか、その利点や用途を知ることは非常に重要です。

○基本的な概念と利点

遅延格納プロパティは、インスタンスの初期化時には値が設定されず、初めてそのプロパティにアクセスされた時点で初期化が行われる特性を持っています。

これにより、次のような利点が得られます。

  1. リソースの節約:初期化に時間やリソースを要するオブジェクトの場合、それが実際に使用されるまで初期化を遅らせることで、リソースの無駄を避けることができます。
  2. 循環参照の回避:オブジェクト同士が相互に参照するようなケースでは、遅延格納プロパティを使用することで、循環参照を避けたり、初期化の順序の問題を緩和することができます。
  3. 柔軟な初期化:遅延格納プロパティを使用することで、プロパティの初期化タイミングや条件を柔軟に制御することができます。例えば、外部のデータに基づいてプロパティを初期化する場合などに有効です。

●遅延格納プロパティの使い方

Swiftの遅延格納プロパティは、その名前の通り、初めてアクセスされるタイミングで初期化されるプロパティです。

この性質により、リソースを効率的に利用することが可能となります。

それでは、この遅延格納プロパティの使い方をいくつかのサンプルコードとともに詳しく見ていきましょう。

○サンプルコード1:基本的な使い方

遅延格納プロパティはlazyキーワードを使用して定義します。

ここでは、遅延格納プロパティの基本的な使い方を表すサンプルコードを紹介します。

class SampleClass {
    lazy var sampleProperty: String = {
        print("初期化します。")
        return "Hello, Swift!"
    }()
}

let instance = SampleClass()
print("遅延格納プロパティにアクセスする前")
print(instance.sampleProperty)
print("遅延格納プロパティにアクセスした後")

このコードでは、SampleClassというクラス内に遅延格納プロパティsamplePropertyを定義しています。

この例では、プロパティが初めてアクセスされる際に、”初期化します。”というメッセージが表示され、その後に”Hello, Swift!”という文字列を返すようになっています。

このサンプルコードを実行すると、”遅延格納プロパティにアクセスする前”というメッセージが表示された後に、初めてsamplePropertyにアクセスされるタイミングで”初期化します。”というメッセージが表示されます。

その後、”Hello, Swift!”という文字列が出力され、最後に”遅延格納プロパティにアクセスした後”と表示される流れとなります。

○サンプルコード2:初期化時の利用例

遅延格納プロパティは、複雑な初期化を伴う場合やリソースを節約したい場合に特に役立ちます。

ここでは、遅延格納プロパティを初期化時に利用するサンプルコードを紹介します。

class ComplexInitialization {
    lazy var complexData: [Int] = {
        var data = [Int]()
        for i in 1...10000 {
            data.append(i)
        }
        return data
    }()
}

let complexInstance = ComplexInitialization()

このコードでは、ComplexInitializationクラス内に、1から10000までの整数を持つ配列complexDataを遅延格納プロパティとして定義しています。

この例では、インスタンスを生成した際にはすぐにcomplexDataが初期化されないため、初期化のコストを遅らせることができます。

実際にcomplexInstance.complexDataにアクセスすると、そのタイミングで1から10000までの整数を持つ配列が生成されます。

○サンプルコード3:計算プロパティとの併用

遅延格納プロパティは、計算プロパティと併用することも可能です。

ここではその利用例を表すサンプルコードを紹介します。

class CalculationSample {
    var baseValue: Int = 10

    lazy var doubledValue: Int = {
        return baseValue * 2
    }()
}

let calculationInstance = CalculationSample()
print(calculationInstance.doubledValue)
calculationInstance.baseValue = 20
print(calculationInstance.doubledValue)

このコードでは、CalculationSampleクラスにbaseValueというプロパティと、それを2倍した値を返す遅延格納プロパティdoubledValueを定義しています。

この例では、初めてdoubledValueにアクセスする際に、その時点のbaseValueの値を2倍した値が返されます。

このサンプルコードを実行すると、まず初めてdoubledValueにアクセスした時の値、つまり10の2倍である20が出力されます。

しかし、その後でbaseValueの値を20に変更しても、再度doubledValueにアクセスしても同じ値である20が出力されることに注意が必要です。

遅延格納プロパティは、初めてアクセスされた後は再計算されないため、このような挙動となります。

●遅延格納プロパティの応用例

遅延格納プロパティはSwiftでのプログラミングにおいて非常に役立つ機能の一つです。

ここでは、遅延格納プロパティのさまざまな応用例をサンプルコードを交えてご紹介します。

○サンプルコード4:複雑な初期化を伴うオブジェクト

遅延格納プロパティは、複雑な初期化を必要とするオブジェクトの生成を遅延させる際に有効です。

下記のコードでは、コンピュータリソースを多く使用するオブジェクトの初期化を遅延させています。

class HeavyResource {
    init() {
        print("HeavyResourceが初期化されました")
    }
}

class ResourceManager {
    lazy var resource: HeavyResource = HeavyResource()
}

let manager = ResourceManager() // ここではHeavyResourceはまだ初期化されない
print("ResourceManagerが生成されました")
let _ = manager.resource       // ここでHeavyResourceが初期化される

このコードではHeavyResourceという名前のリソースを持つResourceManagerクラスを定義しています。

HeavyResourceの初期化は遅延格納プロパティを使って遅延されており、リソースが必要とされるまで初期化されません。

この例では、manager.resourceにアクセスするときに初めてHeavyResourceが初期化されます。

○サンプルコード5:リソースの効率的な利用

遅延格納プロパティは、不必要にリソースを使用することなく、必要になったタイミングでのみ初期化するための方法としても利用されます。

下記のコードは、データベース接続の例を表しています。

class DatabaseConnection {
    init() {
        print("データベースに接続しました")
    }
}

class AppManager {
    lazy var dbConnection: DatabaseConnection = DatabaseConnection()
}

let app = AppManager() // ここではデータベースに接続しない
print("AppManagerが生成されました")
let _ = app.dbConnection  // ここでデータベースに接続する

このコードでは、DatabaseConnectionというデータベース接続を管理するクラスがあり、それが遅延格納プロパティとしてAppManagerクラス内で定義されています。

この例では、app.dbConnectionにアクセスされるまでデータベース接続は行われません。

○サンプルコード6:UIViewControllerとの組み合わせ

遅延格納プロパティは、UIKitのUIViewControllerとの組み合わせでも有効に利用できます。

特に、Viewやその他のUIコンポーネントの初期化を遅延させる場面で役立ちます。

下記のコードは、UIButtonの初期化を表しています。

import UIKit

class SampleViewController: UIViewController {
    lazy var button: UIButton = {
        let btn = UIButton(type: .system)
        btn.setTitle("Click Me", for: .normal)
        btn.frame = CGRect(x: 50, y: 50, width: 150, height: 50)
        return btn
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(button)
    }
}

このコードでは、SampleViewControllerというUIViewControllerのサブクラスを定義しています。

この中で、遅延格納プロパティを使用してUIButtonの初期化を遅延させています。

この例では、ViewControllerのviewDidLoadメソッド内でボタンが初めて参照されるときに、ボタンが初期化されます。

●遅延格納プロパティの注意点と対処法

Swiftの遅延格納プロパティは非常に便利であり、さまざまなシチュエーションで利用されています。

しかし、適切に使用しないと予期しない問題が発生する可能性があるため、注意が必要です。

ここでは、遅延格納プロパティの主な注意点とその対処法について、サンプルコードを交えて詳しく解説していきます。

○サンプルコード7:マルチスレッド環境での扱い

遅延格納プロパティは、最初にアクセスされたときにのみ初期化されるため、マルチスレッド環境では同時に初期化が試みられるリスクがあります。

この時、不安定な状態や意図しない動作を引き起こす可能性があるため注意が必要です。

class SampleClass {
    lazy var sampleProperty: Int = {
        print("Initializing...")
        return 10
    }()
}

let instance = SampleClass()
DispatchQueue.global().async {
    print(instance.sampleProperty)
}
DispatchQueue.global().async {
    print(instance.sampleProperty)
}

このコードでは、samplePropertyを2つの異なるスレッドで同時にアクセスしています。

これにより、”Initializing…”という文字列が複数回表示される可能性があることを表しています。

対処法として、DispatchQueueNSLockなどのロックメカニズムを使用して、初期化の部分を排他制御する方法があります。

しかし、過度なロックはパフォーマンスの低下を引き起こす可能性があるため、必要最低限のロックを心がけることが大切です。

○サンプルコード8:初期化失敗のハンドリング

遅延格納プロパティの初期化は、失敗することがあります。

例えば、外部のAPIからデータを取得する際に通信エラーが発生するなどの理由で、初期化に失敗する可能性があります。

このような場合、適切にエラーハンドリングを行うことが重要です。

class NetworkResource {
    lazy var data: Data? = {
        // 仮の通信処理を示す
        return try? fetchDataFromAPI()
    }()

    func fetchDataFromAPI() throws -> Data {
        // ここでは仮の通信処理を示す
        throw NSError(domain: "com.example", code: 1, userInfo: nil)
    }
}

let resource = NetworkResource()
if let data = resource.data {
    print("Data fetched!")
} else {
    print("Failed to fetch data.")
}

この例では、fetchDataFromAPIメソッドでエラーが発生していることを表しています。

dataプロパティの初期化時にtry?を使用することで、エラーが発生した場合にはnilが返されるようにしています。

その後、初期化が成功したかどうかをチェックして処理を分岐しています。

初期化に失敗する可能性がある場合は、適切なエラーハンドリングを行い、プログラムの安定性を保つことが重要です。

●遅延格納プロパティのカスタマイズ方法

Swiftの遅延格納プロパティは、初回アクセス時まで値の初期化を遅らせることができる非常に便利な機能です。

この機能を活用すれば、初期化コストが高いオブジェクトや、初期化が必要ない場合にリソースを節約することができます。

しかし、この遅延格納プロパティを更にカスタマイズし、より柔軟に利用する方法も存在します。

今回は、遅延格納プロパティのカスタマイズについて、サンプルコードを交えながら解説していきます。

○サンプルコード9:カスタムsetterとgetterの組み合わせ

Swiftのプロパティは、setterとgetterをカスタマイズすることができます。

遅延格納プロパティも例外ではなく、このカスタムsetterとgetterを使用して独自の振る舞いを実装することができます。

ここでは、カスタムsetterとgetterを遅延格納プロパティと組み合わせた例を紹介します。

class CustomProperty {
    private var _value: Int?

    var value: Int {
        get {
            if _value == nil {
                _value = 10  // ここで初期化を行っている
            }
            return _value!
        }
        set {
            _value = newValue
        }
    }
}

このコードでは、_valueというprivateの変数を使用して、遅延格納プロパティのような動作を実現しています。

valueプロパティが初めてアクセスされた際に、_valueがnilであれば10で初期化します。そして、setterを使用して_valueを変更することができます。

このサンプルコードを使用すると、次のような動作を実現できます。

let custom = CustomProperty()
print(custom.value)  // 10と表示される
custom.value = 20
print(custom.value)  // 20と表示される

カスタムsetterとgetterの組み合わせを活用することで、遅延格納プロパティの振る舞いを更にカスタマイズし、独自のロジックを追加することができます。

○サンプルコード10:プロパティオブザーバとの連携

遅延格納プロパティは、プロパティオブザーバと直接組み合わせることができませんが、カスタムgetterとsetterを使用することで間接的に連携させることができます。

ここでは、遅延格納プロパティのような動作を持つカスタムプロパティと、プロパティオブザーバを組み合わせたサンプルコードを紹介します。

class ObserverProperty {
    private var _value: Int?

    var value: Int {
        get {
            if _value == nil {
                _value = 10  // ここで初期化を行っている
            }
            return _value!
        }
        set {
            willSet(newValue) {
                print("新しい値:\(newValue)がセットされます。")
            }
            _value = newValue
            didSet {
                print("値が\(newValue)に変更されました。")
            }
        }
    }
}

このコードでは、willSetdidSetというプロパティオブザーバを利用して、プロパティの値が変更される前後に特定の処理を実行するようにしています。

このサンプルコードを使用すると、次のような動作を実現できます。

let observer = ObserverProperty()
observer.value = 20  // "新しい値:20がセットされます。"、"値が20に変更されました。"と表示される

カスタムgetterとsetterを使用することで、遅延格納プロパティとプロパティオブザーバを連携させることができ、より柔軟なプロパティの管理が可能となります。

まとめ

Swiftの遅延格納プロパティは、初回アクセス時に値の初期化を遅らせることができる強力な機能です。

これにより、リソースの効率的な利用や初期化コストの節約など、多くの利点が得られます。

本記事では、遅延格納プロパティの基本的な使い方からカスタマイズ方法まで、詳細にわたって解説しました。

特に、カスタムsetterやgetterを組み合わせることで、遅延格納プロパティの振る舞いをより柔軟にカスタマイズすることができる点を紹介しました。

また、プロパティオブザーバとの連携を通じて、プロパティの変更を検知し、特定の処理を実行する方法についても触れました。

Swiftを学ぶ上で、遅延格納プロパティは欠かせない知識の一つです。

本記事が、遅延格納プロパティの理解と実践的な利用に役立つ参考資料となれば幸いです。