web-dev-qa-db-ja.com

Swiftのdo-try-catch構文

Swift 2で新しいエラー処理を理解しようとしています。これが私のしたことです。最初にエラーenumを宣言しました。

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

そして、私はエラーをスローするメソッドを宣言しました(例外ではありません。エラーです)。これがその方法です。

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

問題は呼び出し側からです。このメソッドを呼び出すコードは次のとおりです。

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

do行の後にErrors thrown from here are not handled because the enclosing catch is not exhaustiveと書いてあります。しかし私の意見ではそれは徹底的です。なぜならSandwichError enumには2つのケースしかないからです。

通常のswitchステートメントでは、Swiftはすべてのケースが処理されるときにそれが網羅的であることを理解できます。

150
mustafa

Swift 2のエラー処理モデルには、網羅性と回復力という2つの重要なポイントがあります。一緒に、彼らはあなたが投げることができることを知っているものだけでなく、考えられるすべてのエラーをキャッチする必要があるあなたのdo/catchステートメントに煮詰まります。

関数がどんな種類のエラーを投げることができるかを宣言しないことに注意してください、それがまったく投げるかどうかだけ。それは、無限大の問題です。他の人(将来の自分を含む)が使用するための関数を定義しているので、関数のすべてのクライアントを自分の実装のすべての変更に適応させる必要はありません。どんなエラーを投げることができるかを含む関数。あなたは自分の関数を呼び出すコードがそのような変化に対して回復力があることを望みます。

あなたの関数はどんな種類のエラーを投げる(あるいは将来投げるかもしれない)かを言うことができないので、それをキャッチするcatchブロックはそれがどんなタイプのエラーを投げるかもわかりません。だから、あなたが知っているエラータイプを扱うことに加えて、あなたは普遍的なcatchステートメントであなたがそうしないものを扱う必要があります - そのようにあなたの関数が将来投げるエラーのセットを変えてもそのエラー.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

しかし、それだけではありません。この回復力についてもう少し考えてください。サンドイッチをデザインした方法では、エラーを使用するすべての場所にエラーを記述する必要があります。つまり、エラーケースのセットを変更するたびに、それらを使用するすべての場所を変更する必要があるということです。それほど面白くありません。

独自のエラータイプを定義することの背後にある考え方は、そのようなことを集中させることです。エラーに対して description メソッドを定義できます。

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try Sudo"
        }
    }
}

そしてエラー処理コードはあなたのエラータイプにそれ自身を記述するように頼むことができます - 今あなたがエラーを処理するすべての場所が同じコードを使うことができ、そして将来起こりうるエラーケースも処理することができます。

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

これは、エラータイプ(またはそれらの拡張)が他のエラー報告方法をサポートするための道を開きます。たとえば、エラーをiOSユーザーに報告するためのUIAlertControllerを提示する方法を知っているエラータイプの拡張を持つことができます。 。

247
rickster

これはまだ正しく実装されていないと思います。 Swift Programming Guide は、コンパイラが 'switchステートメントのように'徹底的な一致を推測できることを暗示しているようです。徹底的にするために一般的なcatchが必要であることについては何も述べていません。

また、エラーはブロックの終わりではなくtry行にあることにも気付くでしょう。つまり、ある時点で、コンパイラはブロック内のどのtryステートメントが未処理の例外タイプを持っているかを特定できるようになります。

ドキュメントは少しあいまいです。 「Swiftの新機能」のビデオを見てみたところ、手掛かりが見つかりませんでした。私は挑戦し続けます。

更新:

ErrorType推論のヒントはなく、現在はベータ3までです。私は今、これが計画されていたとしたら(そしてそれはまだある時点でそれをしていたと思いますが)、プロトコル拡張への動的ディスパッチはおそらくそれを殺したと信じます。

Beta 4アップデート:

Xcode 7b4では、Throws:のdoc commentサポートが追加されました。これは、「どんなエラーがスローされる可能性があるのか​​、そしてその理由を文書化するために使用されるべきです」。私はこれが少なくともAPIコンシューマにエラーを伝えるためのsomeメカニズムを提供すると思います。あなたがドキュメンテーションを持っているとき誰がタイプシステムを必要としています!

もう一つの更新:

自動ErrorType推論を期待してそのモデルの限界が何かを考え出した後、私は考えを変えました - これ は何ですか代わりにAppleが実装することを願っています。基本的に

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

もう一つのアップデート

アップルのエラー処理の理論的根拠は、こちら から入手できますSwift-evolution メーリングリストでも興味深い議論がありました。基本的に、John McCallは、ほとんどのライブラリがいずれにしても一般的なエラーケースを含むことになると考えており、型付けされたエラーが定型文以外のコードに大きく追加されることはほとんどないと考えているためです。 Chris Lattner氏は、Swift 3がレジリエンスモデルで機能するのであれば、タイプミスを受け入れやすいと述べた。

26
Sam

Swiftはあなたのcase文が全てのcaseを網羅しているわけではないことを心配しています、それを修正するためにあなたはデフォルトcaseを作成する必要があります:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}
4
Icaro

私は関数が投げることができる型の欠如にも失望しました、しかし私は今@ricksterのおかげでそれを手に入れますそして私はそれをこのように要約するつもりです。

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

問題は、myFunctionThatThrowsで何も変更しなくても、MyErrorにエラーケースを追加しただけの場合です。

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

私たちのdo/try/catchはもはや網羅的ではなく、MyErrorを投げる関数を呼んだ他の場所でもないので、私たちは気が狂っています

3
greg3z
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

今すぐ番号を検証します。

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }
0
Yogendra Singh