Swift エラーハンドリングの基本


この記事は何?

Swiftプログラミングでのエラー処理について、調べました。
忘備録を兼ねて投稿します。
参考記事: Swift Language Guide

実行環境

Swift5.x
Xcode11 beta2
macOS 10.14 Mojave

ハンズオン

エラーを定義して、スローするプログラムを記述します。

こんなクラスがあったとして

自動販売機をコードで表現しています。

自動販売機のクラス
// 商品の金額と残り個数
struct ItemInfo {
    var price: Int
    var count: Int
}

// 自動販売機
class VendingMachine {
    var inventories = [String: ItemInfo]()  // 在庫の商品
    var coinsDeposited = 0  // 預かり金

    // 商品を販売
    func vend(itemNamed name: String) {
        let selectedItem = inventories[name]
        coinsDeposited -= selectedItem.price

        var newItem = selectedItem
        newItem.count -= 1
        inventories[name] = newItem   // updating a value

        print("Dispensing \(name)")
    }
}

自販機のインスタンス

自動販売機のインスタンス drinkStand を生成して、"Coke" を買います。

自販機インスタンスを生成する
let drinkStand = VendingMachine()

// ドリンクを補填
drinkStand.inventories = [
    "Coke" : ItemInfo(price: 120, count: 7),
    "Soda" : ItemInfo(price: 80, count: 3),
    "Milk" : ItemInfo(price: 100, count: 10)]

// コーラを買う
drinkStand.vend(itemNamed: "Coke")

起こりうるエラー

この自販機プログラムでは、以下のようなエラーが発生するかもしれません。

  • 無効な商品を選択
  • 支払額が不足
  • 在庫なし

自販機で起こりうるエラー を列挙型を使って、コードで定義します。
このとき、Error プロトコルに準拠させておくと、Swiftが自動的にエラーハンドリングの対象にしてくれます。

エラーの定義
enum VendingMachineError: Error {
    case invalidSelection   // 無効な選択
    case insufficientFunds(coinsNeeded: Int)    // 支払額が不足
    case outOfStock // 在庫なし
}

Error プロトコル

具体的な実装はなく、カラのプロトコルです。
準拠に適合するために、何か特別なことをする必要はありません。

エラーをthrow(スロー)する

自販機クラス内のエラーが発生しそうな行で、エラーをスロー します。
throw キーワードを使います。
どんなエラーが発生したかは、VendingMachineError 型を使って指定できます。
この時点で、vend(itemNamed:) メソッドはエラーを throw するかもしれないメソッドになりました。
メソッド宣言のパラメータ直後に throws キーワードを記述します。

エラーをスローする
class VendingMachine {
    var inventories = [String: ItemInfo]()
    var coinsDeposited = 0

    func vend(itemNamed name: String) throws {
        // 無効な商品を選択したら、エラーをスローする
        guard let selectedItem = inventories[name] else {
            throw VendingMachineError.invalidSelection
        }
        coinsDeposited -= selectedItem.price

        var newItem = selectedItem
        newItem.count -= 1
        inventories[name] = newItem

        print("Dispensing \(name)")
    }
}

エラーをスローする関数の定義

throw キーワードと throws キーワードは間違いやすいので、気をつける必要があります。

スロー関数の宣言(例)
// エラーをスローできる関数の宣言 
func canThrowErrorsMethod() throws -> <ReturnType>

// エラーをスローしない関数の宣言
func cannotThrowErrorsMethod() -> <ReturnType>