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
が複数書かれていた場合は、最後に宣言したものから逆順に実行されます。
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
のありがたみをあまり感じませんが、必ず使い所はあると思います。
出力の順番について
- "いらっしゃいませ!"
- 購入可能な場合はアイテムとアイテムの個数+"お買い上げありがとうございました", エラーの場合はエラーに適した内容を出力
- "またのお越しをお待ちしております。"
実行するとこんな感じになります。
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を使って確認してたんですけど楽で便利ですね。
まとめ
プロダクトレベルでの使用となるとエラーのパターンも増えるでしょうし、考えないといけないことが多くなるので不安が残りますが、信頼性の高いコードを書けそうな気がしています。
ちゃんと勉強して自在に使いこなせるようになりたいです。