Swiftのエラー処理とは?エラーハンドリングの書き方を初心者向けに解説

プログラムは、ファイルが見つからない・通信が失敗する・入力が想定外だったなど、うまくいかない状況に必ず遭遇します。それを想定して書くのがエラー処理(エラーハンドリング)です。

この記事では、Swiftのエラー処理の書き方を、サンプルコードつきで解説します。

Swiftのエラー処理の全体像

Swiftのエラー処理は、次の4つの要素で構成されます。

要素 役割
Errorプロトコル エラーの型を定義する
throws エラーを投げる可能性がある関数に付ける
throw 実際にエラーを投げる
do-catch エラーを受け止める

PythonやJavaScriptの例外処理と考え方は似ていますが、Swiftは「この関数はエラーを出す可能性がある」ことを型で明示する点が異なります。

エラーを定義する

まず、どんなエラーがあり得るかを列挙型で定義します。

enum InputError: Error {
    case empty
    case tooLong(max: Int)
    case invalidCharacter
}

Error プロトコルに準拠させるのが決まりです。tooLong(max: Int) のように、エラーに情報を持たせることもできます。

エラーを投げる

エラーを出す可能性がある関数には throws を付けます。

func validate(_ name: String) throws -> String {
    if name.isEmpty {
        throw InputError.empty
    }
    if name.count > 10 {
        throw InputError.tooLong(max: 10)
    }
    return name
}

throws が付いた関数は、呼び出す側で必ずエラー処理を書く必要があります。書かなければコンパイルエラーになります。

この仕組みにより、エラー処理の書き忘れを防げます。

エラーを受け止める:do-catch

do {
    let name = try validate("Taro")
    print("OK: \(name)")
} catch InputError.empty {
    print("名前が入力されていません")
} catch InputError.tooLong(let max) {
    print("\(max)文字以内で入力してください")
} catch {
    print("その他のエラー: \(error)")
}

ポイントは3つです。

  • エラーを出す可能性のある呼び出しには try を付ける
  • catch はエラーの種類ごとに分けて書ける
  • 最後の catch(条件なし)ですべてを受け止める

catch InputError.tooLong(let max) のように書くと、エラーが持っている値を取り出せます。

try の3つの書き方

書き方 動作 使いどころ
try do-catchで処理する 基本形
try? 失敗したら nil を返す 失敗しても構わない場合
try! 失敗したらアプリが停止 原則避ける
// try? : オプショナルになる
let name = try? validate("")
print(name ?? "入力エラー")      // nil なので「入力エラー」

// try! : 失敗するとクラッシュする(危険)
// let name2 = try! validate("")

try! は絶対に失敗しないと確信できる場合以外は使わないでください。失敗した瞬間にアプリが落ちます。

guard と組み合わせる

条件を満たさないときに早めに関数を抜ける書き方です。ネストが深くなるのを防げます。

func register(_ name: String) {
    guard !name.isEmpty else {
        print("名前を入力してください")
        return
    }
    guard name.count <= 10 else {
        print("10文字以内にしてください")
        return
    }
    print("登録しました: \(name)")
}

guard は「条件を満たさなければ抜ける」という書き方です。if でネストを重ねるより読みやすくなります。

エラーメッセージを分かりやすくする

LocalizedError に準拠させると、エラーに説明文を持たせられます。

enum InputError: LocalizedError {
    case empty
    case tooLong(max: Int)

    var errorDescription: String? {
        switch self {
        case .empty:
            return "名前が入力されていません"
        case .tooLong(let max):
            return "\(max)文字以内で入力してください"
        }
    }
}

do {
    _ = try validate("")
} catch {
    print(error.localizedDescription)   // 名前が入力されていません
}

画面にメッセージを表示する場合、この形にしておくと扱いやすくなります。

SwiftUIでエラーメッセージを表示する

struct FormView: View {
    @State private var name = ""
    @State private var message: String?

    var body: some View {
        VStack {
            TextField("名前", text: $name)
            Button("登録") {
                do {
                    _ = try validate(name)
                    message = nil
                } catch {
                    message = error.localizedDescription
                }
            }
            if let m = message {
                Text(m).foregroundColor(.red)
            }
        }
        .padding()
    }
}

エラーメッセージを @State の変数に入れておき、値があるときだけ表示する、という形が基本です。

オプショナルとエラーの使い分け

Swiftには「値がないかもしれない」を表すオプショナルと、「処理が失敗した」を表すエラーの2つがあります。

状況 使うもの
値がないことが普通にあり得る オプショナル(String?)
失敗の理由を伝えたい エラー(throws)

たとえば「辞書にキーがない」はオプショナル、「入力値が不正」はエラー、という使い分けが自然です。

まとめ

  • エラーは Error プロトコルに準拠した列挙型で定義する
  • エラーを出す関数には throws、投げるときは throw
  • 受け止めるのは do-catch。呼び出しには try が必要
  • try? は失敗時に nil、try! は失敗時にクラッシュするため原則避ける
  • guard を使うとネストを浅く保てる
  • LocalizedError に準拠させると説明文を持たせられる

Swiftの基本文法はSwiftの使い方|基本文法とサンプルコードで解説しています。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら