Swiftのdefer文の完全解説!実践的な方法10選

Swiftのdefer文をイラスト付きで解説する記事のサムネイルSwift

 

【当サイトはコードのコピペ・商用利用OKです】

このサービスはASPや、個別のマーチャント(企業)による協力の下、運営されています。

記事内のコードは基本的に動きますが、稀に動かないことや、読者のミスで動かない時がありますので、お問い合わせいただければ個別に対応いたします。

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

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

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

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

はじめに

近年、プログラミング言語Swiftが注目を浴びています。

SwiftはiOSやmacOS、watchOS、tvOSのアプリケーション開発に主に使用される言語で、その中でも特にユニークな機能の一つにdefer文があります。

この記事では、Swiftのdefer文について、初心者から中級者までが実際に使うシーンでの理解を深めることを目的として、徹底的に解説していきます。

●Swiftのdefer文とは

Swiftのdefer文は、あるスコープが終了する際(関数の終了やスコープからの脱出時など)に、必ず実行されるコードブロックを指定するための文です。

つまり、関数やメソッドが終了する前に、一定の処理を行うことを保証したい場合に、defer文を使用します。

例えば、ファイルのオープンやネットワーク接続の開始などのリソースを確保する操作の後、そのリソースを適切に解放する必要がある場合、リソースの解放処理をdefer文内に書くことで、必ずその処理が実行されることを保証することができます。

また、Swiftのdefer文は、他の言語にも似たような機能が存在しますが、Swift特有の構文や挙動を持っています。

そのため、Swiftを使用する際は、このdefer文の特性や使い方を正しく理解しておくことが重要です。

○defer文の基本概念

defer文の基本的な構文は非常にシンプルです。

deferキーワードの後に、実行したい処理をブロックとして記述します。

このブロック内の処理は、包含するスコープ(関数やメソッド、ループなど)が終了する際に実行されます。

●defer文の詳細な使い方

Swift言語において、defer文は非常に便利な機能の一つです。特にリソースの後処理や、確実に実行させたいコードを記述する際に役立ちます。

ここでは、defer文の使い方を実践的なサンプルコードとともに詳細に解説していきます。

○サンプルコード1:基本的なdefer文の使用

このコードでは、基本的なdefer文の使い方を表しています。

この例では、関数の終了直前に指定した処理を実行しています。

func exampleFunction() {
    print("関数が呼び出されました。")

    defer {
        print("関数が終了します。")
    }

    print("何らかの処理を行います。")
}

exampleFunction()

このコードを実行すると、次の順序で出力がされます。

関数が呼び出されました。
何らかの処理を行います。
関数が終了します。

defer内の処理は、関数の最後、つまりreturn前や関数の終わりの直前に実行されることがわかります。

○サンプルコード2:複数のdefer文の実行順序

このコードでは、複数のdefer文を使用した場合の実行順序を確認します。

この例では、どの順序でdefer文が記述されたかによって、その実行順序がどう変わるかを見ています。

func multipleDefer() {
    print("関数開始")

    defer {
        print("defer1")
    }

    defer {
        print("defer2")
    }

    print("関数中の処理")
}

multipleDefer()

実行すると次のような出力になります。

関数開始
関数中の処理
defer2
defer1

複数のdefer文がある場合、最後に記述されたdefer文から順に実行されることが確認できます。

○サンプルコード3:defer文でのリソースの解放

このコードでは、defer文を使ってリソースの解放を行っています。

この例では、ファイルのオープンとクローズの処理を模しています。

class File {
    func open() {
        print("ファイルを開きます。")
    }

    func close() {
        print("ファイルを閉じます。")
    }
}

func workWithFile() {
    let file = File()
    file.open()

    defer {
        file.close()
    }

    print("ファイルの読み書き処理をします。")
}

workWithFile()

このコードを実行すると、次のような結果が得られます。

ファイルを開きます。
ファイルの読み書き処理をします。
ファイルを閉じます。

●defer文の応用例

Swift言語を使っている方なら、defer文の存在を知っているかもしれません。

しかし、日常的なプログラムの中での具体的な活用方法やそのメリットをしっかりと把握している方はまだまだ少ないのが現状です。

ここでは、defer文をより実践的に使うための応用例をいくつか取り上げ、サンプルコードとともに解説します。

○サンプルコード4:エラーハンドリングと組み合わせた利用

エラーハンドリングは、Swiftプログラミングの中で非常に重要な要素の一つです。

しかし、エラー発生時に一定のクリーンアップ処理を行いたい場合、defer文が大変役立ちます。

このコードでは、do-catch文を使用してエラーハンドリングを行いつつ、defer文でエラーが発生した場合にも保証されるクリーンアップ処理を実装しています。

この例では、ファイルのオープンを試みて、エラーが発生した際にはそれをキャッチしつつ、ファイルのクローズ処理を必ず行うことを保証しています。

func openAndProcessFile(named filename: String) {
    let file: File?
    do {
        file = try File.open(filename)
        defer {
            // このクローズ処理は、関数の終了時またはエラー発生時にも実行される
            file?.close()
        }
        // ファイルの処理ロジック
    } catch {
        print("ファイルのオープンに失敗しました: \(error)")
    }
}

上記のサンプルコードを実行すると、もしファイルオープンに失敗した場合、”ファイルのオープンに失敗しました: (error)”というメッセージが表示されます。

しかし、関数の最後にはfile?.close()が呼び出され、ファイルは正常にクローズされることが保証されます。

○サンプルコード5:ファイル操作時の安全なクローズ処理

defer文はファイルのオープンやクローズ処理に特に適しています。

複雑なファイル操作の中で、何かしらの理由で途中で処理が中断された場合でも、ファイルを安全にクローズすることができます。

このコードでは、ファイルをオープンした後、いくつかの操作を行いつつ、最後に必ずファイルをクローズする処理を実装しています。

この例では、ファイルの読み書き処理中に何らかのエラーが発生しても、defer文によりクローズ処理が保証されています。

func manipulateFile(named filename: String) {
    let file: File?
    do {
        file = try File.open(filename)
        defer {
            // このクローズ処理は、関数の終了時に必ず実行される
            file?.close()
        }
        // ファイルの読み書きやその他の操作
    } catch {
        print("エラーが発生しました: \(error)")
    }
}

上記のサンプルコードを実行すると、ファイル操作中にエラーが発生した場合でも、”エラーが発生しました: (error)”というメッセージが表示される一方で、ファイルは正常にクローズされることが保証されます。

○サンプルコード6:defer文を使った計測処理

Swiftでのプログラミング中、特定の処理の実行時間を計測したい場面があります。

処理時間の計測は、パフォーマンスチューニングやボトルネックの特定に役立ちます。

Swiftでは、defer文を利用して、計測処理を簡潔に書くことができます。

ここでは、defer文を使って、ある処理の実行時間を計測するサンプルコードを紹介します。

import Foundation

func measureExecutionTime() {
    let startTime = CFAbsoluteTimeGetCurrent()

    defer {
        let endTime = CFAbsoluteTimeGetCurrent()
        let elapsedTime = endTime - startTime
        print("この処理にかかった時間: \(elapsedTime)秒")
    }

    // ここに計測したい長い処理やタスクを書く
    for _ in 1...1000000 {
        // 何らかの処理
    }
}

このコードでは、CFAbsoluteTimeGetCurrent関数を使って、現在の絶対時間を取得しています。

そして、defer文内で、処理の終了時の時間を取得し、開始時からの経過時間を計算します。

この例では、ループ内でシンプルな処理を1,000,000回実行しており、この処理の所要時間を計測します。

実行すると、print関数により、”この処理にかかった時間: X.XX秒”という形式で、計測された実行時間がコンソールに表示されます。

○サンプルコード7:ネストした関数内での利用

Swiftの関数内で更に別の関数を定義・実行する場面があります。

このようなネストした関数の中でも、defer文を使うことで、特定のスコープの終了時に特定の処理を保証することができます。

ここでは、ネストした関数内でdefer文を利用したサンプルコードの例を紹介します。

func outerFunction() {
    print("外部関数の開始")

    func innerFunction() {
        print("内部関数の開始")

        defer {
            print("内部関数の終了")
        }

        // 何らかの処理
        print("内部関数の処理中")
    }

    innerFunction()

    print("外部関数の終了")
}

このコードを実行すると、次の順番でコンソールにメッセージが表示されます。

外部関数の開始
内部関数の開始
内部関数の処理中
内部関数の終了
外部関数の終了

こちらの例では、innerFunctionという内部関数内でdefer文を使用しており、内部関数が終了する際に”内部関数の終了”というメッセージが出力されることが保証されています。

○サンプルコード8:defer文とは直接関係ない処理の区切り

Swiftのdefer文は、関数やメソッドから抜ける際に必ず実行される処理を記述するためのものですが、必ずしも関数のメインの目的と直接関係する処理だけを記述するわけではありません。

実際に、defer文の中には関数のメインの処理とは直接関係のない区切りの処理も含めることができます。

例えば、関数の実行時間を計測したい場合、関数の開始時と終了時の時間を取得し、その差分を計算することで実行時間を計算することができます。

この時、関数の終了時の時間取得という処理は、関数のメインの目的とは直接関係ありませんが、defer文を使って記述することで、関数からのリターンの際に必ず実行されることを保証することができます。

import Foundation

func measureExecutionTime() {
    let startTime = Date()

    // 何らかの処理...
    for _ in 0...1000000 {
        _ = 1 + 1
    }

    defer {
        let endTime = Date()
        let executionTime = endTime.timeIntervalSince(startTime)
        print("実行時間: \(executionTime)秒")
    }
}

measureExecutionTime()

このコードではDate()を使って関数の実行開始時間を取得しています。

その後、繰り返し処理を行い、defer文内で関数の終了時間を取得し、実行時間を計算して出力しています。

この例では、繰り返し処理を実行している間の時間を計測しています。

このコードを実行すると、実行時間が秒単位で表示されることがわかります。

○サンプルコード9:defer文を使ったUIの更新

SwiftのGUIフレームワークであるUIKitでは、UIの変更はメインスレッドで行う必要があります。

非同期処理やバックグラウンドスレッドで実行される処理の中で、UIの更新を行いたい場合は、その処理が終了した際にメインスレッドに移動してUIの更新を行う必要があります。

このような場面で、defer文を使用して、関数の終了時に必ずメインスレッドでUIの更新を行うことができます。

下記のサンプルは、非同期処理の終了時にUILabelのテキストを更新する例です。

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!

    func updateLabelTextAsync() {
        DispatchQueue.global().async {
            // 何らかのバックグラウンド処理...
            defer {
                DispatchQueue.main.async {
                    self.label.text = "非同期処理が完了しました"
                }
            }

            // 長時間かかる処理など...
        }
    }
}

このコードでは、非同期処理の中でdefer文を使用して、その非同期処理が終了した際にメインスレッドに移動してUILabelのテキストを更新しています。

この例では、DispatchQueue.global().asyncを使って非同期処理を行い、その中でdefer文を使用しています。

このコードを実行すると、非同期処理が完了するとラベルのテキストが「非同期処理が完了しました」と表示されることがわかります。

○サンプルコード10:非同期処理との組み合わせ

defer文と非同期処理を組み合わせることで、非同期処理が終了した際に特定の処理を行うことができます。

これにより、リソースの解放や後処理など、非同期処理後に必ず行いたい処理を安全に実装することができます。

下記のサンプルコードは、非同期処理の中でファイルの読み書きを行い、その後でファイルをクローズする例です。

import Foundation

func asyncFileOperation() {
    let fileURL = URL(fileURLWithPath: "/path/to/file.txt")

    DispatchQueue.global().async {
        guard let fileHandle = try? FileHandle(forWritingTo: fileURL) else {
            return
        }

        defer {
            fileHandle.closeFile()
        }

        // ファイルの読み書き処理...

        fileHandle.write("Hello, Swift!".data(using: .utf8)!)
    }
}

このコードでは、非同期処理の中でファイルハンドルを取得し、ファイルの読み書き処理を行った後、defer文を使用してファイルハンドルをクローズしています。

この例では、非同期処理が完了した際に、必ずファイルハンドルをクローズすることが保証されています。

このコードを実行すると、非同期処理が完了するとファイルが正しくクローズされることがわかります。

●注意点と対処法

defer文を使用する際には、いくつかの重要な注意点があります。

これらを理解し、適切な対処法をとることで、defer文をより効果的に活用することが可能です。

○defer文の実行タイミング

defer文は、それが置かれたスコープ(通常は関数またはメソッド)の終了時に実行されます。

これには正常終了だけでなく、エラーによる途中終了時も含まれます。

しかし、この挙動は予期せぬ動作を引き起こす原因となることがあります。

defer文が実行されるタイミングは、文が書かれた場所ではなく、スコープの終端に依存することを理解しておく必要があります。

したがって、複雑な関数内でdefer文を使う際は、その実行タイミングを意識することが重要です。

対処法として、実行タイミングに混乱を招かないように、defer文は関数またはスコープの始めに書くことが望ましいです。

これにより、読み手がdefer文の存在とそのスコープを容易に認識できます。

○適切な使い方とミスを避けるための方法

defer文は、リソースの解放やファイルのクローズ、ネットワーク接続の断など、クリーンアップが必要な作業に適しています。

しかし、不適切な使い方をするとプログラムの可読性を低下させたり、予期せぬ挙動を引き起こすことがあります。

defer文内で重い処理を行うことは避けるべきです。

また、defer文内で発生する可能性のあるエラーに対する適切な処理を検討する必要があります。

さらに、defer文が複数ある場合、それらは逆の順序で実行されることを意識する必要があります。

対処法として、クリーンアップが必要なリソースを取り扱う際にはdefer文を使用し、コードの可読性を高めます。

重い処理や複雑なロジックはdefer文の外で行い、defer文内では簡潔かつ明確な処理に留めることが肝要です。

複数のdefer文を使用する場合は、それらが逆順に実行されることを頭に入れておきましょう。

●カスタマイズ方法

Swiftのdefer文は非常に強力なツールであり、多様な場面での利用が考えられます。

しかし、その真価は実際のプロジェクトにおいて、具体的なニーズに応じてカスタマイズされた形で利用されることでより一層際立ちます。

今回は、defer文の応用テクニックとカスタマイズ例を紹介していきます。

○defer文の応用テクニックとカスタマイズ例

Swift言語において、defer文は基本的に関数やメソッドが終了する際に実行されるブロックを定義することができる文です。

しかし、この基本的な動作をベースに、さまざまな応用やカスタマイズが可能です。

□条件付きの実行

このコードでは条件分岐を使って特定の条件下でのみdeferブロック内のコードを実行する方法を表しています。

   func processWithCondition(shouldRun: Bool) {
       if shouldRun {
           defer {
               print("特定の条件下でのみ実行されます")
           }
       }

       print("関数の処理")
   }

   processWithCondition(shouldRun: true)

この例では、shouldRuntrueの場合のみdeferブロック内のprint関数が実行されます。

実行すると次のような出力が得られます。

関数の処理
特定の条件下でのみ実行されます

□複数の処理をまとめて実行

このコードでは複数のdefer文を組み合わせて、複数の処理を順番に実行する方法を表しています。

   func multipleDefer() {
       defer {
           print("最初に定義されたdefer文")
       }

       defer {
           print("次に定義されたdefer文")
       }

       print("関数の本体の処理")
   }

   multipleDefer()

defer文は定義された逆の順序で実行されるため、この例では次に定義されたdefer文が先に、そして最初に定義されたdefer文が後に実行されます。

実行すると次の出力が得られます。

関数の本体の処理
次に定義されたdefer文
最初に定義されたdefer文

□ローカル変数の利用

このコードではdefer文内で関数のローカル変数を使用する方法を表しています。

   func useLocalVariable() {
       var message = "初期のメッセージ"

       defer {
           print(message)
       }

       message = "変更されたメッセージ"
   }

   useLocalVariable()

この例では、defer文が実行されるタイミングでのmessage変数の値が出力されます。

実行すると次の出力が得られます。

変更されたメッセージ

まとめ

Swiftのdefer文は、関数やメソッドの終了時に実行されるコードブロックを定義するための強力なツールです。

この記事では、defer文の基本的な使い方から、実践的な応用例、注意点、そしてカスタマイズ方法まで幅広く解説しました。

これらの知識をもとに、Swiftのプログラミングにおいて、リソースの解放やエラーハンドリング、さまざまな処理の後始末など、様々なシーンでdefer文を効果的に利用することができます。

また、具体的なサンプルコードを通じて、defer文の動作原理や利用シーンを理解することで、実際の開発においてもこの機能を最大限に活用して、より品質の高いコードを書くことができるでしょう。

Swiftを学ぶすべての方々にとって、defer文の理解と適切な使用は、コードの安全性や保守性を向上させるための重要なステップとなります。

これからもSwiftの機能やテクニックを継続的に学び、日々の開発に役立てていくことで、より高度なアプリケーション開発が可能となるでしょう。

本記事が、皆さんのSwiftにおける学習や実践の一助となれば幸いです。