Xcode
Swift

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

More than 3 years have passed since last update.

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


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


do try catch

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


throws throw

throwthrowsがつけられたメソッド内で使用する。throwErrorTypeプロトコルを適合したenumとともに使用します。

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


guard

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

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


defer

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

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

    func printNumber() {

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

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


サンプル

上記内容を含んだサンプルを書いてみました。基本的にThe Swift Programming Languageに書かれているサンプルコードが元にして、ちょっといじりました。


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

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

enum VendingMachineError: ErrorType {

case InvalidSection // 不正な選択
case InsufficientFunds(required: Int) // 金額不足
case OutOfStock // 在庫切れ
}


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

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

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をつけています。

    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の出番です。

    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. "またのお越しをお待ちしております。"

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

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を使って確認してたんですけど楽で便利ですね。


まとめ

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

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