[iOS] swift2でもエラーハンドリングしたい NSError, do catch

2016/04/2

こんにちは。きんくまです。

ブログのアクセスログを読むと、iOSのエラーハンドリングの記事がそこそこアクセスがあります。
ただ、この書き方だとswift2だと動かないので申し訳なく思いまして。なのでswift2版を書いておこうかと。
2番目の記事のエラーと例外のところはswiftでも考え方は同じなので役に立つかと思われます。

>> [iOS] swiftでもエラーハンドリングしたい!
>> [iOS] NSErrorでエラーハンドリングしたい

ちなみにこの文はAppleさんの公式のドキュメント見ながら書いているので、詳しくはそこを読んでいただければ。

>> The Swift Programming Language (Swift 2.2): Error Handling

手短に書き方だけ説明。do catch 構文

まずは使い方です。この構文を知っておけばとりあえずはなんとかなります。

do {
    try xxxxx()
} catch {
    print("error")
}

エラーの内容がしりたければこちら

do {
    try xxxxx()
} catch let error as NSError {
    print("error \(error)")
}

xxxxx のところで throws を返すかもしれないメソッドを呼び出してください。
こういうやつです。

func xxxxx() throws 

もしエラーがそこで投げられたら catch 構文が呼ばれます。

エラーを自分で作る

ここからはエラーを自分で作ってみます。

今回のお題
割り算をしたときのエラーを処理する
割り算関数はこうしました。num1をnum2で割った答えを返します。

func divide(num1:Double, num2:Double) throws -> Double? {
    return num1 / num2
}

いまの状態だと何のチェックもなく値を返しています。ここで2つのエラーを考えてみます。


1. 数値を0で割ったらエラー(つまり num2 == 0)

2. 2つの引数が num1 < num2 だったらエラー

2番目の条件はふつうは全く必要ありませんが、今回説明するためにたまたまそういう仕様だったと思ってください

エラーを定義します。enum でまとめて、個別のエラーは case で書きます。

enum CalculationError: ErrorType {
    case DivideByZero
    case Num1LessThanNum2
}

エラーを投げる

さきほどのdivide関数を、引数によってエラーを投げるようにしてみます

func divide(num1:Double, num2:Double) throws -> Double? {

    guard num2 != 0 else {
        throw CalculationError.DivideByZero
    }

    guard num1 >= num2 else {
        throw CalculationError.Num1LessThanNum2
    }

    return num1 / num2
}

guard というキーワードの構文が出てきました。guard文は引数のチェックなどに使うと便利な構文です。エラーハンドリング以外のときでも使えます。

guard エラーにならない条件 else {
    エラーになったらこの中に入る
    //この中で return, break, continue, throw のいずれかを呼ぶ必要がある
}

こんな感じに使うと便利みたいです。

func greeting(message:String?) -> String? {
    guard let myMessage = message else {
        return nil
    }
    //ここから先はMyMessageはアンラップされた状態でnilじゃないことが保証されます
    return "\(myMessage) world!"
}

let text:String = "hello"
let greetResult:String? = greeting(text) //hello world

let text2:String? = nil
let greetResult2:String? = greeting(text2) //nil

参考
>> Swift 2.0 で追加された guard のいいところ - Qiita

構文で1点注意するところがあり、guard のあとはエラーに「なる」条件ではなくエラーに「ならない」条件を書くということです。guard文と同じことはif文を使っても書くことができます。さきほどの divide を if 文でチェックしてみます。条件式がguardのときと逆になっていることにチェックです。

func divide(num1:Double, num2:Double) throws -> Double? {

    if num2 == 0 {
        throw CalculationError.DivideByZero
    }
    
    if num1 < num2 {
        throw CalculationError.Num1LessThanNum2
    }
    
    return num1 / num2
}

guardのとき

guard num2 != 0 else {}
guard num1 >= num2 else {}

ifのとき

if num2 == 0 {}
if num1 < num2 {}

if文の方が、エラーのときの条件式のままなのでわかりやすいかなーとも思います。

エラーを投げる関数を使う

var n1:Double = 1
var n2:Double = 2
var result:Double?

do {
    result = try divide(n1, num2: n2)
    
} catch CalculationError.DivideByZero {
    print("Calculation Error: num2 is zero")
    
} catch CalculationError.Num1LessThanNum2 {
    //この場合はここが呼ばれる
    print("Calculation Error: num1 \(n1) < num2 \(n2)")
    
} catch {
    print("unexpected error")
}

print("result \(result)")

上の例文では n1 < n2 なので 2番目の catch が呼ばれます。 n1, n2 を適当な値に変更したときの挙動を試してみるといいかもです。(n2 = 0 など)

2016/04/07 追記

do catch のところは catch文を全部書かなくてもこれでもいけました

do {
    result = try divide(n1, num2: n2)
    
} catch let error as CalculationError {
    print("CalculationError: \(error)")  //CalculationError: Num1LessThanNum2
    
    //Appleで推奨してない書き方だけど、上でエラーのカテゴリをざっくりわけたあとに振り分けも可能
    switch error {
    case CalculationError.DivideByZero:
        print("switch DivideByZero")
        break
    case CalculationError.Num1LessThanNum2:
        print("switch Num1LessThanNum2")
        break
    default:
        break
    }

} catch {
    print("unexpceted Error'")
}

まとめ

swift2でのエラーハンドリングでした。do catch, guard などの使い方をチェックですね。

今回のサンプルコード全文。(Playgroundで試しました)

import UIKit

enum CalculationError: ErrorType {
    case DivideByZero
    case Num1LessThanNum2
}

func divide(num1:Double, num2:Double) throws -> Double? {
    guard num2 != 0 else {
        throw CalculationError.DivideByZero
    }
    
    guard num1 >= num2 else {
        throw CalculationError.Num1LessThanNum2
    }
    
    return num1 / num2
}

var n1:Double = 3
var n2:Double = 0
var result:Double?

do {
    result = try divide(n1, num2: n2)
    
} catch CalculationError.DivideByZero {
    print("Calculation Error: num2 is zero")
    
} catch CalculationError.Num1LessThanNum2 {
    print("Calculation Error: num1 \(n1) < num2 \(n2)")
    
} catch {
    print("error")
}

print("result \(result)")
LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る