1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Swiftのguard文を理解するための10本ノック

Last updated at Posted at 2025-02-13

Swiftのguard文の使い方を実例とともに紹介します。各例では、実際のコードと、その動作やポイントについて、分かりやすく解説しています。ぜひ参考にして、あなたのプロジェクトにも取り入れてみてください!

guard文の基本

Swiftのguard文は、コードの冒頭で条件をチェックし、条件が満たされなかった場合に早期に処理を中断(早期リターン)するための便利な制御構造です。これにより、メインのロジック部分をネストせずに記述でき、コードの可読性と保守性が向上します。

guard 条件 else {
    // 条件が成立しない場合の処理(return, break, continue, throw などで早期に抜ける必要があります)
    print("条件不成立")
}
// ここに、条件を満たした場合の処理を書く
print("条件成立")

guard文では、指定した条件がfalseの場合にelseブロックが必ず実行され、その中で現在のスコープから抜け出す(例えば、関数からreturnする)必要があります。これにより、guard文以降のコードは、必ず条件が成立していることが保証されるため、安全に処理を進めることができます。

使い所

  • オプショナル値の安全な取り扱い:

ユーザーからの入力や外部APIからのデータなど、予期しないnilが発生する可能性がある場合に、guard文で早めにチェックして安全にアンラップする。

  • 入力の検証:

関数の引数が期待する条件(例えば、特定の範囲内の数値や正しいフォーマットの文字列など)を満たしているかどうかを検証し、問題があれば処理を中断する。

  • ループ内での条件チェック:

ループの各イテレーションで条件を確認し、条件を満たさない場合にそのループをスキップする(continueなどを使って)。

それではコード例を見ていきましょう!

1: オプショナルの値を安全にアンラップする

func greet(name: String?) {
    guard let unwrappedName = name else {
        // nameがnilでないことを確認し、非オプショナルな変数unwrappedNameに束縛する
        print("名前がありません")
        return
    }
    // guardを通過した後は、unwrappedNameは必ず値を持っている
    print("こんにちは、\(unwrappedName)さん!")
}

greet(name: "太郎")  // 出力: こんにちは、太郎さん!
greet(name: nil)     // 出力: 名前がありません。

解説

この例では、guard文を使ってオプショナルのアンラップと、num > 0という条件の両方を同時にチェックしています。いずれかの条件が満たされなければ、elseブロックが実行され、エラーメッセージを出力して関数を終了します。

2: オプショナルのアンラップと条件のチェックを同時に行う


func checkNumber(_ number: Int?) {
    // numberがnilでなく、かつ正の数であることを確認
    guard let num = number, num > 0 else {
        print("有効な正の数を入力してください。")
        return
    }
    print("入力された数は \(num) です。")
}

checkNumber(5)    // 出力: 入力された数は 5 です。
checkNumber(-3)   // 出力: 有効な正の数を入力してください。
checkNumber(nil)  // 出力: 有効な正の数を入力してください。

解説

この例では、guard文を使ってオプショナルのアンラップと、num > 0という条件の両方を同時にチェックしています。いずれかの条件が満たされなければ、elseブロックが実行され、エラーメッセージを出力して関数を終了します。

3: 複数のオプショナルを一度にアンラップする


func displayUserInfo(name: String?, age: Int?) {
    // nameとageの両方が有効で、かつageが0以上であることを確認
    guard let validName = name, let validAge = age, validAge >= 0 else {
        print("有効な名前と年齢を提供してください。")
        return
    }
    print("\(validName)さんは \(validAge) 歳です。")
}

displayUserInfo(name: "花子", age: 25)  // 出力: 花子さんは 25 歳です。
displayUserInfo(name: nil, age: 25)      // 出力: 有効な名前と年齢を提供してください。
displayUserInfo(name: "花子", age: -5)    // 出力: 有効な名前と年齢を提供してください。

解説

ここでは、2つのオプショナル変数nameとageを同時にアンラップし、さらにageが負の値でないかをチェックしています。どれか1つでも条件を満たさなければ、elseブロックが実行され、関数は早期に終了します。

4: 配列が有効かつ空でないかをチェックする

func printFirstElement(of array: [Int]?) {
    // 配列がnilでなく、かつ空でないことを確認
    guard let array, !array.isEmpty else {
        print("配列がくうか無効です")
        return
    }
    print("配列の最初の要素は\(array[0])です。")
}

printFirstElement(of: [10, 20, 30])  // 出力: 配列の最初の要素は 10 です。
printFirstElement(of: [])           // 出力: 配列が空か無効です。
printFirstElement(of: nil)          // 出力: 配列が空か無効です。

解説

この関数は、引数として渡される配列がnilでなく、かつ空でないかをguard文でチェックしています。条件に合わない場合は、エラーメッセージを出力して関数を終了します。正常な場合のみ、配列の最初の要素を出力します。

5: ループ内での条件チェックにguardを使用する

let numbers = [10, -5, 20, 0, 15]
for num in numbers {
    // 正の数であるかをチェック。そうでなければこのイテレーションをスキップする
    guard num > 0 else {
        print("\(num) は正の数ではありません。スキップします。")
        continue
    }
    print("\(num) は正の数です。")
}
/*
実行結果
10 は正の数です。
-5 は正の数ではありません。スキップします。
20 は正の数です。
0 は正の数ではありません。スキップします。
15 は正の数です。
*/

解説

この例では、配列numbersの各要素に対して、guard文を使って値が正の数かどうかをチェックしています。もしnumが正の数でなければ、elseブロック内でメッセージを出力し、continueを使って次のループにスキップします。条件を満たす場合のみ、その数値に対して処理を続けます。

6: イニシャライザ内での条件チェック(failable initializer)

struct Person {
    let name: String
    let age: Int
    
    // イニシャライザでオプショナルをチェックし、条件を満たさなければ初期化に失敗(nil)とする
    init?(name: String?, age: Int?) {
        guard let name = name, let age = age, age >= 0 else {
            return nil
        }
        self.name = name
        self.age = age 
    }
}

if let person = Person(name: "太郎", age: 30) {
    print("Person: \(person.name), \(person.age)")
} else {
    print("無効な値が渡されました。")
}

// Person: 太郎, 30



if let person = Person(name: nil, age: 30) {
    print("Person: \(person.name), \(person.age)")
} else {
    print("無効な値が渡されました。")
}

// 無効な値が渡されました

解説

この例では、構造体Personのイニシャライザ内でguard文を用いて、nameとageが有効かつageが0以上であるかをチェックしています。条件を満たさなければnilを返すため、初期化が失敗します。guard文により、初期化前に必要な前提条件を明確に確認できます。

7: guard文とエラースローによる例外処理

enum LoginError: Error {
    case invalidCredentials
}

func login(username: String?, password: String?) throws {
    // ユーザー名とパスワードが非nilかつ空文字でないことを確認
    guard let username = username, let password = password, !username.isEmpty, !password.isEmpty else {
        throw LoginError.invalidCredentials 
    }
    print("ユーザー \(username) がログインしました。")
}

do {
    try login(username: "user123", password: "secuewPassword")
    try login(username: nil, password: "password")
} catch {
    print("ログインエラー: \(error)")
}

// ユーザー user123 がログインしました。
// ログインエラー: invalidCredentials

解説

この例では、guard文を使ってログインに必要なユーザー名とパスワードが正しく提供されているかをチェックしています。条件を満たさなければ、LoginError.invalidCredentialsエラーをスローして、呼び出し側にエラーを伝えます。エラーハンドリングと組み合わせることで、より堅牢なコードが実現できます。

8: 非同期処理内でのguard文による前提チェック

import Foundation

func fetchData(from urlString: String, completion: @escaping (Result<String, Error>) -> Void) {
    // URL文字列から有効なURLオブジェクトを生成できるか確認
    guard let url = URL(string: urlString) else {
        completion(.failure(NSError(domain: "InvalidURL", code: -1, userInfo: nil)))
        return
    }
    
    // 擬似的な非同期処理(実際のネットワーク処理の代わり)
    DispatchQueue.global().async {
        // ここでは常に成功と仮定
        let result = "データ取得成功: \(url.absoluteString)"
        completion(.success(result))
    }
}

fetchData(from: "invalid_url") { result in
    switch result {
    case .success(let data):
        print(data)
    case .failure(let error):
        print("エラー: \(error.localizedDescription)")
    }
}

解説

この例では、非同期処理を行う関数内でguard文を使用し、URL文字列が有効なURLに変換できるかをチェックしています。無効な場合は、直ちにcompletionクロージャをエラー結果とともに呼び出して処理を終了しています。非同期処理における早期リターンのパターンとして有用です。

9: 数値が特定の範囲内にあるかのチェック


func processScore(_ score: Int) {
    // スコアが0から100の範囲内であることを確認
    guard (0...100).contains(score) else {
        print("スコアは0から100の範囲でなければなりません")
        return
    }
    print("スコア \(score) を処理中")
}


processScore(85)   // 出力: スコア 85 を処理中...
processScore(150)  // 出力: スコアは0から100の範囲でなければなりません。

解説

ここでは、guard文を使って数値が0から100の範囲内にあるかどうかをチェックしています。条件に合わなければエラーメッセージを出力して関数を終了するため、以降の処理は常に有効なスコアを前提に行えます。

10: 列挙型のパターンマッチングとguard文の組み合わせ

enum ProcessState {
    case ready, running, completed
}

func startProcess(state: ProcessState) {
    // 状態が.readyでなければ早期に処理を終了する
    guard case .ready = state else {
        print("プロセスを開始できません。現在の状態: \(state)")
        return
    }
    print("プロセスを開始します。")
}

startProcess(state: .ready)
// プロセスを開始します

startProcess(state: .running)
// プロセスを開始できません。現在の状態: running

解説

この例では、guard文とパターンマッチング(guard case)を組み合わせ、列挙型ProcessStateが期待する状態(.ready)であるかを確認しています。期待する状態でなければ早期に処理を中断し、エラーメッセージを表示することで、後続の処理が不適切な状態で実行されるのを防ぎます。

以上の例を通して、guard文がさまざまなシーン(イニシャライザ、エラーハンドリング、非同期処理、範囲チェック、パターンマッチング)でどのように使えるかを理解できるでしょう。guard文を用いることで、条件が満たされない場合の早期退出が容易になり、コードの可読性と安全性が向上します。

備考

この記事は、あるチャットでの質疑応答内容をもとにまとめたものです。各種サンプルコードや考え方は、実際のプロジェクトや仕様に合わせて適宜ご調整ください

1
3
1

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
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?