5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Revise Error handling in Swift

Posted at
1 / 29

内容

  • 目的: 今一度swift2のエラーハンドリングを復習しておこう
  • 目次:
    • Error handling in objc
      • NSError
    • Error handling in swift
      • try, do-catch, defer
      • Yet another approach: Results

Error handling in objc


NSError

  • unix process
    • 0 ... 正常終了
    • 1~255 ... 異常
  • NSError
    • code: Int
    • domain: String. code重複の防止
    • userInfo: [NSObject: AnyObject]?
      • reasonやメッセージを渡す

(NSError **) を最後の引数に入れるパターン

NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] moveItemAtPath:@"/path/to/target"
                                                       toPath:@"/path/to/destination"
                                                        error:&error];
if (!success) {
    NSLog(@"%@", error);
}

コールバックの最後の引数に (NSError *)を入れるパターン

NSURL *URL = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request
            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (!data) {
        NSLog(@"%@", error);
    } else {
        // ...
    }
}] resume];

問題点

  • 冗長
  • エラーチェックを忘れがち
    • エラーチェックを忘れてもコンパイルエラーにはならない
  • エラー時のclose処理などが簡潔に書けない
    • goto?

Error handling in swift


Error handling in swift

  • 簡潔に書きたい
  • エラー処理忘れをコンパイル時にチェックしたい
  • closeなどの復帰処理を簡潔に

ErrorType

  • ErrorTypeは空のprotocol
  • enumでもstructでもclassでも良い
  • throw文を使ってerrorを投げる
    • func canThrowErrors() throws -> String

Handling Error

  • 4つのハンドリング方法
    • propagation
    • do-catch
    • optional value
    • assert

Swift book より

When a function throws an error, it changes the flow of your program, so it’s important that you can quickly identify places in your code that can throw errors

どこでエラーが起こるのかが、すぐに特定できることが重要


コスト

  • Swift error handlingはcall stackを辿ったりはしない
  • コストはほぼ通常のreturnと同じ

Propagating Errors Using Throwing Functions

  • tryキーワードをメソッド呼び出しの前につける
  • try vendingMachine.vend(itemNamed: name)

Handling Errors Using do-catch

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    print(error)
}

Handling Errors Using do-catch

  • do節内でErrorが投げられたら, マッチするcatch節に処理が移る
    • pattern matching(where)
  • patternを書かないとerrorにbindされる(default)
  • 必ず全てをcatchする必要はなくて、一部のエラーは伝搬させるということもできる

Converting Errors to Optional Values

  • try? を使うと結果をoptionalで取得できる
let x = try? someThrowingFunction()
 
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

Converting Errors to Optional Values

  • すぐにreturnする場合など、処理が単純な場合は簡潔に書ける
func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

Assert

  • try!
  • エラーが基本発生しないとわかっている時は、値をunwrapしてruntimeにassertionさせる

let photo = try! loadImage("./Resources/John Appleseed.jpg")


Cleanup Actions

  • finally?
    • つ defer
func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}

まとめ

  • 簡潔に書きたい
    • try!, try?, guard, let, defer
  • エラー処理忘れをコンパイル時にチェックしたい
    • throws でエラーの発生可能性明示
    • コンパイル時にチェック
  • closeなどの復帰処理を簡潔に
    • defer

他言語との比較

  • Java: 検査例外
    • 無視することができない
    • catch (Exception e){}による握りつぶしが横行
  • C#
    • 非検査例外
    • catch し忘れ
  • Swift
    • 検査例外に近い
    • tryでエラーの起こる箇所の明示
    • 実行時のオーバーヘッドが少ない

Results

  • Result : 「成功かもしくは失敗」を表現した型
  • Optionalにさらに付加情報を加えたもの
  • メソッドの戻り値にResultを使う
    • パターンマッチが使える

enum Result {
   case .Success(T)
   case .Error(E)
}
enum Optional<T> {
    case .Some(T)
    case .None
}

antitypical/Result

  • デファクト
  • ReactiveCocoa, APIKitなどいろんなライブラリで使われている
  • map and flatMap, making Result more composable than throw
  • materialize/dematerialize でthrowsと変換

func stringForKey(json: JSONObject, key: String) -> Result<String, JSONError>  {
 ...
}

switch stringForKey(json, key: "email") {
case let .Success(email):
    print("The email is \(email)")
case let .Failure(JSONError.NoSuchKey(key)):
    print("\(key) is not a valid key")
case .Failure(JSONError.TypeMismatch):
    print("Didn't have the right type")
}

map, flatMap, mapError, flatMapError

let idResult = intForKey(json, key:"id").map { id in String(id) }
let keyStringVal = stringForKey(json, key: "key").flatMap {
    stringForKey(json, key: $0)
}
  • Errorの型が合わない時はmapError, flatMapErrorで型を合わせる
  • Rustのtry! macroはこのアプローチをさらに書きやすくしたもの

今後への期待

  • 非同期例外
    • C#, JavaScriptのような async await catch
    • 言語側でのResultのサポート

References

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?