web-dev-qa-db-ja.com

Swift言語でのエラー処理

私はSwiftについてあまり読みませんでしたが、気付いたことの1つは例外がないということです。では、Swiftでエラー処理はどのように行われるのでしょうか。誰かがエラー処理に関連するものを見つけましたか?

185
peko

スイフト2&3

Swift 2では、新しいエラー処理メカニズムがあるため、状況が少し変わりました。これは、例外とやや似ていますが詳細が異なります。

1.エラーの可能性を示す

関数/メソッドがエラーをスローする可能性があることを示したい場合は、このようにthrowsキーワードを含める必要があります。

func summonDefaultDragon() throws -> Dragon

注:関数が実際にスローできるエラーの種類に関する指定はありません。この宣言は、関数がErrorTypeを実装する任意の型のインスタンスをスローできること、またはまったくスローしていないことを示しています

2.エラーが発生する可能性がある関数を呼び出す

関数を呼び出すには、このようにtryキーワードを使う必要があります。

try summonDefaultDragon()

この行は通常、次のようにdo-catchブロックとして存在します。

do {
    let dragon = try summonDefaultDragon() 
} catch DragonError.dragonIsMissing {
    // Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
    // Other specific-case error-handlng
} catch {
    // Catch all error-handling
}

注:catch節はSwiftパターンマッチングの強力な機能をすべて使用しているので、ここでは非常に柔軟です。

自分自身がthrowsキーワードでマークされている関数からスロー関数を呼び出している場合は、エラーを伝播させることにします。

func fulfill(quest: Quest) throws {
    let dragon = try summonDefaultDragon()
    quest.ride(dragon)
} 

あるいは、try?を使ってthrowing関数を呼び出すこともできます。

let dragonOrNil = try? summonDefaultDragon()

このようにして、何らかのエラーが発生した場合は、戻り値またはnilを取得します。このようにしてエラーオブジェクトを取得することはありません。

つまり、try?を以下のような便利なステートメントと組み合わせることもできます。

if let dragon = try? summonDefaultDragon()

または

guard let dragon = try? summonDefaultDragon() else { ... }

最後に、エラーが実際には発生しないことを知っていることを確認し(たとえば、既に確認済みであることが前提条件であるため)、try!キーワードを使用します。

let dragon = try! summonDefaultDragon()

関数が実際にエラーをスローした場合は、アプリケーションにランタイムエラーが発生し、アプリケーションは終了します。

3.エラーを投げる

エラーをスローするには、このようにthrowキーワードを使用します。

throw DragonError.dragonIsMissing

ErrorTypeプロトコルに準拠したものなら何でも投げることができます。初心者のためにNSErrorはこのプロトコルに準拠していますが、おそらく次のように追加のデータの断片で、複数の関連するエラーをグループ化することを可能にするenumベースのErrorTypeで行きたいと思うでしょう

enum DragonError: ErrorType {
    case dragonIsMissing
    case notEnoughMana(requiredMana: Int)
    ...
}

新しいSwift 2および3のエラーメカニズムとJava/C#/ C++スタイルの例外との主な違いは次のとおりです。

  • 構文は少し異なります。do-catch + try + deferと従来のtry-catch-finally構文。
  • 例外処理では通常、成功パスよりも例外パスの方がはるかに長い実行時間がかかります。これは、Swift 2.0エラーの場合ではありません。成功パスとエラーパスのコストはほぼ同じです。
  • エラーをスローするコードはすべて宣言する必要がありますが、例外はどこからでもスローされる可能性があります。すべてのエラーは、Javaの命名法では「チェック済み例外」です。ただし、Javaとは対照的に、発生する可能性があるエラーは指定しません。
  • Swiftの例外はObjCの例外と互換性がありません。あなたのdo-catchブロックはNSExceptionをキャッチしないでしょう、そしてその逆も同様です、あなたがObjCを使わなければならないということです。
  • Swiftの例外は、NSError(関数を返すfalse)またはBool(関数を返すnil)を返し、エラーの詳細をAnyObjectを渡すというCocoaのNSErrorPointerメソッド規約と互換性があります。

エラー処理を容易にするための追加の合成糖として、さらに2つの概念があります。

  • java/C#/ etcのfinallyブロックと同じ効果を得るための遅延アクション(deferキーワードを使用)
  • guardキーワードを使用して)guardステートメントを使用すると、if/elseコードを通常のエラーチェック/シグナリングコードよりも少し少なく書くことができます。

スイフト1

ランタイムエラー:

Leandrosがランタイムエラー(ネットワーク接続の問題、データの解析、ファイルを開くなど)の処理について提案しているように、ObjCの場合と同様にNSErrorを使用する必要があります。これは、Foundation、AppKit、UIKitなどがエラーを報告するためです。それは言語のことよりもフレームワークのことです。

使用されているもう一つの頻繁なパターンはAFNetworkingのようにセパレータの成功/失敗ブロックです:

var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
    success: { (NSURLSessionDataTask) -> Void in
        println("Success")
    },
    failure:{ (NSURLSessionDataTask, NSError) -> Void in
        println("Failure")
    })

それでも失敗ブロックはエラーを説明するNSErrorインスタンスを頻繁に受け取りました。

プログラマーのエラー:

プログラマのエラー(配列要素の範囲外アクセス、関数呼び出しに渡された無効な引数など)に対しては、ObjCで例外を使用しました。 Swift言語は例外をサポートしていないようです(throwcatchなどのキーワード)。ただし、ドキュメントに示されているように、ObjCと同じランタイムで実行されているため、まだNSExceptionsをスローすることができます。

NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()

ObjCコードで例外をキャッチすることを選択することはできますが、純粋なSwiftでそれらをキャッチすることはできません。

問題は、プログラマーのエラーに例外をスローするのか、それともAppleが言語ガイドで示唆しているようにアサーションを使用するのかということです。

144
MDJ

更新2015年6月9日 - 非常に重要

Swift 2.0にはtrythrow、およびcatchのキーワードが付属していますが、最も魅力的なものは次のとおりです。

Swiftは、エラーを生成するObjective-Cのメソッドを、Swiftのネイティブエラー処理機能に従ってエラーをスローするメソッドに自動的に変換します。

注意:デリゲートメソッドやNSErrorオブジェクト引数を持つ完了ハンドラを受け取るメソッドなど、エラーを発生させるメソッドは、Swiftによってインポートされたときにスローされるメソッドにはなりません。

抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使用する(Swift 2 Prerelease)」iBooks。

例:(本から)

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
    NSLog(@"Error: %@", error.domain);
}

Swiftと同等のものは次のようになります。

let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
    try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
    print ("Error: \(error.domain)")
}

エラーを投げる:

*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]

自動的に呼び出し元に伝えられます。

throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)

Appleの書籍、The Swift Programming Languageから、エラーはenumを使って処理する必要があるようです。

これは本からの例です。

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")

switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}

アップル社の「The Swift Programming Language」。 https://itun.es/br/jEUH0.l

更新

Appleのニュースブック、「CocoaとObjective-CでSwiftを使う」から。 Swift言語を使用してランタイム例外が発生することはないので、try-catchがないのはそのためです。代わりに Optional Chaining を使用してください。

これは本からの一続きです:

たとえば、以下のコードリストでは、lengthプロパティとcharacterAtIndex:メソッドがNSDateオブジェクトに存在しないため、1行目と2行目は実行されません。 myLength定数はオプションのIntであると推論され、nilに設定されます。 3行目に示すように、if – letステートメントを使用して、オブジェクトが応答しない可能性があるメソッドの結果を条件付きでラップ解除することもできます。

let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
    println("Found \(fifthCharacter) at index 5")
}

抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使う」iBooks。 https://itun.es/br/1u3-0.l


そして本はまたObjective-C(NSError Object)からのココアエラーパターンを使うことを勧めます。

Swiftでのエラー報告は、Objective-Cと同じパターンに従いますが、オプションの戻り値を提供するという追加の利点もあります。最も単純な場合は、関数からBool値を返して、成功したかどうかを示します。エラーの理由を報告する必要がある場合は、関数にNSErrorPointer型のNSError outパラメータを追加できます。このタイプは、Objective-CのNSError **とほぼ同等ですが、メモリの安全性がさらに高まり、オプションの型指定も可能になります。以下のコードリストに示すように、接頭辞&演算子を使用して、オプションのNSError型への参照をNSErrorPointerオブジェクトとして渡すことができます。

var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
    encoding: NSUTF8StringEncoding,
    error: &writeError)
if !written {
    if let error = writeError {
        println("write failure: \(error.localizedDescription)")
    }
}

抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使う」iBooks。 https://itun.es/br/1u3-0.l

Objective-Cのアプローチと同様に、Swiftには例外はありません。

開発段階では、assertを使用して、発生する可能性があるエラーを検出できます。本番に進む前に修正が必要です。

古典的なNSErrorのアプローチは変更されていません。あなたはNSErrorPointerを送ります。

簡単な例:

var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
    println("An error occurred \(error)")
} else {
    println("Contents: \(contents)")
}
13
Leandros

推奨される「Swift Way」は次のとおりです。

func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
    return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}

var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
    println("write failure 1: \(writeError!.localizedDescription)")
    // assert(false) // Terminate program
}

しかし、エラー処理を最後に別のブロックに移動するので、追いかけやすいので、try/catchを使用することをお勧めします。この配置は「ゴールデンパス」と呼ばれることもあります。幸運にもあなたはクロージャでこれをすることができます:

TryBool {
    write("~/Error2")(error: $0) // The code to try
}.catch {
    println("write failure 2: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

再試行機能を追加するのも簡単です。

TryBool {
    write("~/Error3")(error: $0) // The code to try
}.retry {
    println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
    return write("~/Error3r")  // The code to retry
}.catch {
    println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
    // assert(false) // Terminate program
}

TryBoolのリストは次のとおりです。

class TryBool {
    typealias Tryee = NSErrorPointer -> Bool
    typealias Catchee = NSError? -> ()
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return self.retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) {
        var error: NSError?
        for numRetries in 0...retries { // First try is retry 0
            error = nil
            let result = tryee(&error)
            if result {
                return
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        catchee(error)
    }
}

Bool値の代わりにOptionalの戻り値をテストするための同様のクラスを書くことができます。

class TryOptional<T> {
    typealias Tryee = NSErrorPointer -> T?
    typealias Catchee = NSError? -> T
    typealias Retryee = (NSError?, UInt) -> Tryee

    private var tryee: Tryee
    private var retries: UInt = 0
    private var retryee: Retryee?

    init(tryee: Tryee) {
        self.tryee = tryee
    }

    func retry(retries: UInt, retryee: Retryee) -> Self {
        self.retries = retries
        self.retryee = retryee
        return self
    }
    func retry(retryee: Retryee) -> Self {
        return retry(1, retryee)
    }
    func retry(retries: UInt) -> Self {
        // For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
        self.retries = retries
        retryee = nil
        return self
    }
    func retry() -> Self {
        return retry(1)
    }

    func catch(catchee: Catchee) -> T {
        var error: NSError?
        for numRetries in 0...retries {
            error = nil
            let result = tryee(&error)
            if let r = result {
                return r
            } else if numRetries != retries {
                if let r = retryee {
                    tryee = r(error, numRetries)
                }
            }
        }
        return catchee(error)
    }
}

TryOptionalバージョンでは、以降のプログラミングを容易にする、Optional以外の戻り型を強制しています。 'スウィフトウェイ:

struct FailableInitializer {
    init?(_ id: Int, error: NSErrorPointer) {
        // Always fails in example
        if error != nil {
            error.memory = NSError(domain: "", code: id, userInfo: [:])
        }
        return nil
    }
    private init() {
        // Empty in example
    }
    static let fallback = FailableInitializer()
}

func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
    return FailableInitializer(id, error: error)
}

var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
    println("failableInitializer failure code: \(failError!.code)")
    failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap

TryOptionalを使う:

let failure2 = TryOptional {
    failableInitializer(2)(error: $0)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

let failure3 = TryOptional {
    failableInitializer(3)(error: $0)
}.retry {
    println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
    return failableInitializer(31)
}.catch {
    println("failableInitializer failure code: \($0!.code)")
    return FailableInitializer.fallback
}

自動展開に注意してください。

11
Howard Lovatt

編集:この答えはうまくいきますが、それはObjective-Cの音訳にすぎませんスイフトに。 Swift 2.0での変更により廃止されました。上記のGuilherme Torres Castroの答えは、Swiftでのエラー処理の推奨方法の非常に優れた入門書です。 VOS

それを理解するには少し時間がかかりましたが、私はそれを疑ったと思います。それは醜いようです。 Objective-Cバージョンを超える薄いスキン以上のものはありません。

NSErrorパラメータを使って関数を呼び出す...

var fooError : NSError ? = nil

let someObject = foo(aParam, error:&fooError)

// Check something was returned and look for an error if it wasn't.
if !someObject {
   if let error = fooError {
      // Handle error
      NSLog("This happened: \(error.localizedDescription)")
   }
} else {
   // Handle success
}`

エラーパラメータをとる関数を書く...

func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {

   // Do stuff...

   if somethingBadHasHappened {
      if error {
         error.memory = NSError(domain: domain, code: code, userInfo: [:])
      }
      return nil
   }

   // Do more stuff...
}
7

Try Catch機能を提供するObjective Cの基本ラッパー。 https://github.com/williamFalcon/SwiftTryCatch

のように使用します。

SwiftTryCatch.try({ () -> Void in
        //try something
     }, catch: { (error) -> Void in
        //handle error
     }, finally: { () -> Void in
        //close resources
})
5
William Falcon

これはSwift 2.0への更新の答えです。私は、Javaのような機能豊富なエラー処理モデルを楽しみにしています。最後に、彼らは良い知らせを発表しました。 ここ

エラー処理モデル:Swift 2.0の新しいエラー処理モデルは、おなじみのtry、throw、およびcatchのキーワードで即座に自然になります。何よりも、それはApple SDKとNSErrorと完璧に連携するように設計されています。実際、NSErrorはSwiftのErrorTypeに準拠しています。それについての詳細を聞くためにあなたは間違いなくSwiftの新機能に関するWWDCセッションを見たいと思うでしょう。

例:

func loadData() throws { }
func test() {
do {
    try loadData()
} catch {
    print(error)
}}

Guilherme Torres Castroが述べたように、Swift 2.0では、tryname__、catchname__、doname__をプログラミングで使用できます。

たとえば、CoreDataでは、&errorをパラメータとしてmanagedContext.executeFetchRequest(fetchRequest, error: &error)に入れる代わりに、データメソッドを取得します。ここではmanagedContext.executeFetchRequest(fetchRequest)を使用し、tryname__、catchname__( )でエラーを処理するだけです。

do {
   let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
   if let results = fetchedResults{
      people = results
   }
} catch {
   print("Could not fetch")
}

Xcode7 Betaをすでにダウンロードしている場合Documentations and API Referencethrowing errorsを検索して、最初に表示される結果を選択すると、この新しい構文に対して何ができるかという基本的な考え方がわかります。しかし、完全なドキュメントはまだ多くのAPIに投稿されていません。

より手の込んだエラー処理のテクニックは、にあります。

Swiftの新機能(2015セッション106 28分30秒)

3
Zingoer

エラー処理はSwift 2.0の新機能です。 trythrowおよびcatchキーワードを使用します。

公式Apple Swiftブログの Apple Swift 2.0アナウンスを参照してください。

2
babelchips

Swift 2から始めて、他の人がすでに述べたように、エラー処理はdo/try/catchとErrorType enumの使用を通して最もよく達成されます。これは同期メソッドでは非常にうまく機能しますが、非同期エラー処理には少し巧妙さが必要です。

この記事には、この問題に対する優れたアプローチがあります。

https://jeremywsherman.com/blog/2015/06/17/using-Swift-throws-with-completion-callbacks/

要約する:

// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData

// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
    {
    completionHandler()
    }

それから、上記のメソッドの呼び出しは次のようになります。

self.loadData("someString",
    completionHandler:     
        { result: LoadDataResult in
        do
            {
            let data = try result()
            // success - go ahead and work with the data
            }
        catch
            {
            // failure - look at the error code and handle accordingly
            }
        })

これは、非同期関数に個別のerrorHandlerコールバックを渡すよりも少しきれいに見えます。これは、Swift 2より前ではこれがどのように処理されるかでした。

1
Gene Loparco

私が見たことは、デバイスの性質上、ユーザーに大量の不可解なエラー処理メッセージを投げたくないということです。ほとんどの関数がオプションの値を返すのはそのためで、オプションを無視するようにコードを書くだけです。関数が失敗したことを意味するnilに戻らない場合は、メッセージなどをポップすることができます。

0
cheborneck

例外を処理するための素晴らしく単純なlib: TryCatchFinally-Swift

他の人と同じように、Objective Cの例外機能をラップしています。

このように使用してください。

try {
    println("  try")
}.catch { e in
    println("  catch")
}.finally {
    println("  finally")
}
0