Swift do-try-catch構文


162

Swift 2の新しいエラー処理を理解するために試してみます。これが私がしたことです:最初にエラー列挙型を宣言しました:

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列挙型の場合は2つしかないため、それは網羅的です。

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


3
スローするエラーのタイプを指定しないため、Swiftはすべての可能なオプションを決定できません
Farlei Heinen

エラーのタイプを指定する方法はありますか?
mustafa 2015年

新しいバージョンのSwiftブックには何も見つかりません
現在のところ

エラーや警告なしで私のために遊び場で動作します。
Fogmeister 2015年

2
遊び場doは、非網羅的な最上位のブロックを許可するように見えます-スローしない関数でdoをラップすると、エラーが発生します。
Sam

回答:


267

Swift 2のエラー処理モデルには、網羅性と回復力という2つの重要なポイントがあります。まとめると、それらは、スローできることがわかっているエラーだけでなく、考えられるすべてのエラーをキャッチする必要のあるdo/ catchステートメントに要約されます。

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

関数は、どのような種類のエラーをスローするか(または将来的にスローする可能性がある)と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")
}

これにより、エラータイプ(またはその拡張)がエラーを報告する他の方法をサポートできるようになります。たとえば、UIAlertControlleriOSユーザーにエラーを報告するためのを提示する方法を知っているエラータイプの拡張を使用できます。


1
@rickster:コンパイラエラーを本当に再現できますか?元のコードはエラーや警告なしでコンパイルされます。そして、キャッチされない例外がスローされた場合、プログラムはerror caught in main().-で中止されます。つまり、あなたが言ったすべてのことは理にかなっているように見えますが、その動作を再現することはできません。
マーティンR

5
拡張機能でエラーメッセージを区切る方法が気に入っています。コードをクリーンに保つ本当に良い方法です!素晴らしい例!
Konrad77 2015年

tryランタイムエラーが発生し、アプリケーションがクラッシュする可能性があるため、本番用コード
Otar

@Otarは一般的には良い考えですが、少し話題から外れています。答えは、使用(または使用しない)に対応していませんtry!。また、Swiftのさまざまな「強制」操作(ラップ解除、試行など)については、実稼働コードであっても、間違いなく有効で「安全な」使用例があります。前提条件または構成によって障害の可能性を確実に排除した場合テスト不可能なエラー処理コードを書くよりも、瞬時の失敗に短絡する方が合理的です。
リクスター

必要なのがエラーメッセージを表示することだけであれば、そのロジックをSandwichErrorクラス内に配置することは理にかなっています。しかし、私はほとんどのエラーを疑っています。エラー処理ロジックをカプセル化することはできません。これは、通常、呼び出し元のコンテキスト(回復するか、再試行するか、上流で障害を報告するかなど)の知識が必要であるためです。つまり、最も一般的なパターンは、とにかく特定のエラータイプと照合する必要があるということです。
最大

29

これはまだ適切に実装されていないのではないかと思います。スウィフトプログラミングガイドは、間違いなく、コンパイラは「switch文のような」徹底的な試合を推測できることを意味するようです。包括的catchであるために将軍が必要であることについては触れていません。

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

ただし、ドキュメントは少しあいまいです。「Swiftの新機能」ビデオをざっと見てみましたが、手掛かりが見つかりませんでした。頑張ります。

更新:

ErrorType推論のヒントなしで、ベータ3に達しました。私は今、これが計画されていたなら(そして、私はまだそれがいつかそうだったと思います)、プロトコル拡張の動的ディスパッチがおそらくそれを打ち消しました。

Beta 4アップデート:

Xcode 7b4はにdocコメントのサポートを追加しましたThrows:。これは、「どのエラーがスローされる可能性があるか、およびその理由を文書化するために使用する必要があります」。私は、これは、少なくとも提供して推測するいくつかの APIの消費者にエラーを通信するためのメカニズムを。あなたがドキュメントを持っているときに誰が型システムを必要とするのですか!

別の更新:

自動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

さらに別のアップデート

Appleのエラー処理の理論的根拠がここにあります。また、swift-evolutionメーリングリストについても興味深い議論が行われています。基本的に、ジョンマッコールは型付きエラーに反対しています。なぜなら、ほとんどのライブラリはいずれにせよ一般的なエラーケースを含むことになると考えており、型付きエラーがボイラープレート以外のコードに多くを追加する可能性は低いからです(彼は「吸引ブラフ」という用語を使用しました)。Chris Lattner氏は、Swift 3がレジリエンスモデルで機能すれば、タイプエラーを受け入れる可能性があると語った。


リンクをありがとう。ただし、ジョンに説得されていません。「多くのライブラリに「その他のエラー」タイプが含まれている」ということは、誰もが「その他のエラー」タイプを必要としていることを意味するものではありません。
フランクリンユー

明白なカウンターは、関数がどのような種類のエラーをスローするかを知る簡単な方法がないということです。かなり率直に言って、それはむしろ迷惑です。
ウィリアムTフログガード2016

4

スウィフトはあなたのケースステートメントがすべてのケースをカバーしていないことを心配しています、それを修正するにはデフォルトのケースを作成する必要があります:

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")
}

2
しかし、それは厄介ではありませんか?私は2つのケースのみを持ち、それらすべてがcatchステートメントにリストされています。
mustafa 2015年

2
追加するfunc method() throws(YourErrorEnum)、またはthrows(YourEnum.Error1, .Error2, .Error3)何をスローすることができるかを理解するための拡張リクエストの今がよい時です
Matthias Bauch

8
WWDCセッションの1つにあるSwiftコンパイラーチームは、「Javaのような」考えられるすべてのエラーの明確なリストを望んでいないことを明らかにしました。
サム

4
デフォルト/デフォルトエラーはありません。他のポスターが指摘しているように、空白のキャッチ{}を残すだけ
Opus1217

1
@Icaroそれは私を安全にしません。将来「配列に新しいエントリを追加する」場合、コンパイラは影響を受けるすべてのcatch句を更新しないことを私に怒鳴るべきです。
フランクリン・ユー

3

また、関数がスローできるタイプがないことにもがっかりしましたが、@ 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
なぜあなたが台無しにされているのか私にはわかりません。コンパイラエラーが表示されますが、これは正しいですか。これが、列挙型のケースを追加した場合のswitchステートメントの動作です。
Sam

ある意味では、これはエラー列挙型/実行ケースで発生する可能性が最も高いように思われましたが、列挙型/スイッチで発生するのとまったく同じです。私はまだAppleが投げたものをタイプしないという選択が良いものだと自分に納得させようとしているが、あなたは私にこれを手伝ってはくれない!^^
greg3z

スローされたエラーを手動で入力すると、自明ではないケースでは大きな混乱を招くことになります。タイプは、関数内のすべてのthrowステートメントとtryステートメントから発生する可能性のあるすべてのエラーの結合です。エラー列挙型を手動で維持している場合、これは苦痛です。catch {}ただし、すべてのブロックの下部にあるA は間違いなくさらに悪い。コンパイラが最終的にエラーの種類を自動的に推論できることを願っていますが、確認できませんでした。
サム

理論的には、コンパイラーが関数がスローするエラーのタイプを推測できるはずであることに同意します。しかし、開発者が明確にするためにそれらを明示的に書き留めることも理にかなっていると思います。あなたが話している重要なケースでは、さまざまなエラーの種類を一覧表示することは私には問題ないようです:func f()はErrorTypeA、ErrorTypeBをスローします{}
greg3z

エラーの種類を伝えるためのメカニズム(ドキュメントコメント以外)がないという点で、明らかに大きな部分が欠けています。ただし、Swiftチームは、エラータイプの明示的なリストは必要ないと述べています。私は確かに過去に例外を確認するJavaを扱ってきたほとんどの人が同意するだろう😀だ
サム・

1
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)
  }

-2

次のように列挙型を作成します。

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

次のようなメソッドを作成します。

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

エラーが存在するかどうかを確認して処理します。

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}

閉じるが葉巻はない。間隔を固定して、enumキャメルケースを作成してみてください。
アレック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.