do, try, catch, throws, throw, guard, defer 全部使ったサンプル書いてみた

Swift2が発表されてようやく勉強を始めました。とりあえず。`do`, `try`, `catch`, `throws`, `throw`, `guard`, `defer`について調べてみました。

## 追加された`do`などについて

### `do` `try` `catch`
この辺はセットで使います。`do`でスコープを作成して、`try`をつけて`throws`がつけて定義されたメソッドを呼び出し。`catch`節で投げられるであろうエラーをハンドリングする感じです。

### `throws` `throw`
`throw`は`throws`がつけられたメソッド内で使用する。`throw`は`ErrorType`プロトコルを適合した`enum`とともに使用します。

`throw`でエラーが投げられると、`do-catch`節でハンドリングできるようになります。

### `guard`

`guard`は正となるはずの値の確認に使用するので、`if`文に似ています。ただし、`else`を必ずつけなければならず、正でない値だった場合にはなにかしら処理を行います。

`throws`をつけた例外を投げるメソッド内で使用することが多そうです。

### `defer`
スコープから抜ける直前に実行させたい処理がある場合は`defer`を使用するといいみたいです。

`defer`が複数書かれていた場合は、最後に宣言したものから逆順に実行されます。

```swift
    func printNumber() {
        defer { print("1") }
        defer { print("2") }
        defer { print("3") }
        // 3,2,1の順番で出力される
    }
```

例えば`switch`文を使っていろいろ処理をするけど、なんらかの処理は共通して行いたい場合などに便利そうです。

## サンプル

上記内容を含んだサンプルを書いてみました。基本的に[The Swift Programming Language](https://itunes.apple.com/jp/book/swift-programming-language/id881256329?mt=11)に書かれているサンプルコードが元にして、ちょっといじりました。

### `ErrorType`プロトコルでエラータイプを定義

自動販売機のエラータイプを定義しています。書いてあるまんまです。

```swift
enum VendingMachineError: ErrorType {
    case InvalidSection // 不正な選択
    case InsufficientFunds(required: Int) // 金額不足
    case OutOfStock // 在庫切れ
}
```

### `do`, `try`, `catch`, `throws`, `throw`, `guard`, `defer`全部使って構造体作ってみた

自動販売機の構造体。`YEN`とか無駄に定義していますけど、気にしないでください。

```swift
typealias YEN = Int

// 自動販売機内の販売商品
struct Item {
    var price: YEN
    var count: Int
    var name:  String
}

// 自動販売機
struct VendingMachine {

    // 自動販売機内の商品
    var inventory: [Item]
    
    // 投入金額
    var amountDeposited: YEN
    
    // 購入メソッド
    func checkInventory(itemNamed name:String, amount:Int) throws {
        
        // 引数で渡ってきたnameでfilter
        let namedItems = inventory.filter { (item: Item) -> Bool in
            return item.name == name
        }
        
        // Itemはnameで一意になる
        guard let item = namedItems.first else {
            throw VendingMachineError.InvalidSection
        }
        
        // 在庫の確認
        guard item.count >= amount else {
            throw VendingMachineError.OutOfStock
        }
        
        guard amountDeposited >= item.price * amount else {
            let amountRequired = item.price * amount - amountDeposited
            throw VendingMachineError.InsufficientFunds(required: amountRequired)
        }
        
    }
    
    // 購入
    func vend(itemNamed name: String, amount: Int) {
        
        print("いらっしゃいませ！")
        
        defer { print("またのお越しをお待ちしております。") }
        
        do {
            try self.checkInventory(itemNamed: name, amount: amount)
            print("\(name)を\(amount)つですね。お買い上げありがとうございます。")

        } catch VendingMachineError.InvalidSection {
            print("\(name)は取り扱っておりません。")

        } catch VendingMachineError.InsufficientFunds(let moneyRequired) {
            print("\(moneyRequired)円足りません。")
            
        } catch VendingMachineError.OutOfStock {
            print("\(name)は在庫を切らしております。申し訳ございません。")
            
        } catch {
            print("不明なエラーです。")
            
        }
    }
}
```

`checkInventory`メソッド内で`guard`を使用してアイテムの名前のチェック、在庫数、投入金額の確認をしています ~~（itemの名前チェック部分がイマイチなので良いやり方があったら教えていただきたいです）~~ （→ [2015/07/18] filterを使用する処理に変更しました。もうちょっと良くなる？） 。`checkInventory`メソッドには`throws`をつけています。

```swift
    func checkInventory(itemNamed name:String, amount:Int) throws {
        
        // 引数で渡ってきたnameでfilter
        let namedItems = inventory.filter { (item: Item) -> Bool in
            return item.name == name
        }
        
        // Itemはnameで一意になる
        guard let item = namedItems.first else {
            throw VendingMachineError.InvalidSection
        }
        
        // 在庫の確認
        guard item.count >= amount else {
            throw VendingMachineError.OutOfStock
        }
        
        guard amountDeposited >= item.price * amount else {
            let amountRequired = item.price * amount - amountDeposited
            throw VendingMachineError.InsufficientFunds(required: amountRequired)
        }
        
    }
```

渡された名前に該当するアイテムがなかったり、在庫が足りなかったり、金額が不足していた場合は、先ほど定義した`VendingMachineError`の中から適切なものを`throw`するようにしています。

存在しないアイテムや、アイテムが存在しても在庫が足りない場合は、例外を投げます。`guard`を使用してチェックしています。

次に`vend`メソッドの中で`checkInventory`メソッドを呼び出し、アイテムのチェックを行います。`do`, `try`, `catch`の出番です。

```swift
    func vend(itemNamed name: String, amount: Int) {
        
        print("いらっしゃいませ！")
        
        defer { print("またのお越しをお待ちしております。") }
        
        do {
            try self.checkInventory(itemNamed: name, amount: amount)
            print("\(name)を\(amount)つですね。お買い上げありがとうございます。")

        } catch VendingMachineError.InvalidSection {
            print("\(name)は取り扱っておりません。")

        } catch VendingMachineError.InsufficientFunds(let moneyRequired) {
            print("\(moneyRequired)円足りません。")
            
        } catch VendingMachineError.OutOfStock {
            print("\(name)は在庫を切らしております。申し訳ございません。")
            
        } catch {
            print("不明なエラーです。")
            
        }
    }
```

購入不可能な場合は、`catch`節でエラーパターンを確認して、購入ができない理由を出力します。

`vend`メソッド内で`"いらっしゃいませ"`という文字列が最初に出力されるようになっており、購入可能か不可能か関係なしに `"またのお越しをお待ちしております。"`を出力するようにしています。スコープを抜けるときに出力されれば良いので、`defer`を使用しています。

この使い方だと`defer`のありがたみをあまり感じませんが、必ず使い所はあると思います。

#### 出力の順番について
1. "いらっしゃいませ！"
2. 購入可能な場合はアイテムとアイテムの個数+"お買い上げありがとうございました", エラーの場合はエラーに適した内容を出力
3. "またのお越しをお待ちしております。"

実行するとこんな感じになります。

```swift
let inventory:[Item] = [
    Item(price: 120, count: 10, name: "コーラ"),
    Item(price: 150, count:  3, name: "おしるこ"),
    Item(price: 180, count:  1, name: "コーンポタージュ")
]

let vm = VendingMachine(inventory: inventory, amountDeposited: 150)

// いらっしゃいませ！
// おしるこを1つですね。お買い上げありがとうございます。
// またのお越しをお待ちしております。
vm.vend(itemNamed: "おしるこ", amount: 60)

// いらっしゃいませ！
// twizzlersは取り扱っておりません。
// またのお越しをお待ちしております。
vm.vend(itemNamed: "twizzlers", amount: 1)

// いらっしゃいませ！
// 30円足りません。
// またのお越しをお待ちしております。
vm.vend(itemNamed: "コーンポタージュ", amount: 1)
```

Playgroudを使って確認してたんですけど楽で便利ですね。

## まとめ

プロダクトレベルでの使用となるとエラーのパターンも増えるでしょうし、考えないといけないことが多くなるので不安が残りますが、信頼性の高いコードを書けそうな気がしています。

ちゃんと勉強して自在に使いこなせるようになりたいです。