【Swift】Result<Success,Failure>型の使い方!非同期のエラー処理

この記事からわかること

  • Swift列挙型Result使い方
  • Success/Failure違い
  • 非同期処理絡みのエラー処理

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Result<Success,Failure>

Result<Success,Failure>型とは?

@frozen  enum Result<Success,Failure> where Failure : Error

SwiftのResult<Success,Failure>型は成功または失敗の結果の状態と状態に関連付く結果の値を保持することができる列挙型です。エラー処理の代名詞であるtry-catch文を使用しなくてもエラー処理を実装できるため、コードをより簡潔に記述できます。主に使われる場面は非同期処理の結果や、ネットワーク通信などの外部リソースを扱う際などです。

Result型に定義されているメンバーは以下の2つです。

  1. success:成功
  2. failure:失敗

メンバー名からも分かるように成功/失敗の2つが定義されています。定義元を見てみると以下のようになっていました。


@frozen  public enum Result<Success,Failure> { 
    /// A success, storing a `Success` value. 
    case success(Success) 

    /// A failure, storing a `Failure` value. 
    case failure(Failure)
}

引用:GitHub:Result.swift

Associated Value

Result型はAssociated Value(関連型enum)となっているので各メンバーの値に任意の型を持たせることができます。Success/Failureはジェネリクスであり任意の方を指定することができますが FailureはError型に準拠している必要があります。

つまりResult型からは成功したか失敗したかの識別とそれに関連する値(成功ならデータ型、失敗ならエラー型など)を取得することが可能になります。

使い方

まずは変数にResult型を格納してみます。変数の型として明示的にResult<String,Error>を指定しました。これで文字列型とエラー型を保持させます。

let result: Result<String,Error> = .success("成功しました")
print(result) // success("成功しました")

格納する値はメンバー名をドットシンタックスで、値を( )の中に記述します。

失敗した場合は独自のエラー型を定義しておき当てはめることが多いと思います。

enum TestError : Error {
    case overflow
}
let result: Result<String,TestError> = .failure(.overflow)
print(result)

switch文を使用した分岐処理

Result型は列挙型(enum)として定義されているのでswitch文を使用して処理を分岐させることができます。

let result: Result<String,Error> = .success("成功しました")

switch result {
case .success(let str) :
    print(str) // 成功しました
case .failure(let err) :
    print(err)
}

非同期処理に使用する

Result型の使い所として公式によるとエラーが発生する可能性のある非同期処理と記述されていました。

When writing a function, method, or other API that might fail, you use the throws keyword on the declaration to indicate that the API call can throw an error. However, you can’t use the throws keyword to model APIs that return asynchronously. Instead, use the Result enumeration to capture information about whether an asychronous call succeeds or fails, and use the associated values for the Result.success(_:) and Result.failure(_:) cases to carry information about the result of the call.

翻訳:失敗する可能性のある関数、メソッド、またはその他の API を記述する場合、宣言で throws キーワードを使用して、API 呼び出しがエラーをスローできることを示します。ただし、throws キーワードを使用して、非同期に戻る API をモデル化することはできません。代わりに、Result 列挙を使用して、非同期呼び出しが成功したか失敗したかに関する情報を取得し、Result.success(_:) および Result.failure(_:) ケースに関連付けられた値を使用して、呼び出しの結果に関する情報を伝えます。

引用:Result<Success,Failure>

例えば以下は公式に記述されていた「ランダムな数値を非同期的に上限回数まで生成するプログラム」を実装した例です。少し改変して分かりやすくしています。

enum  LimitError: Error {
    case limitReached // 上限に達した
}

struct AsyncRandomGenerator {
    let queue = DispatchQueue.main
    static let upperLimit = 5
    var count = 0

    mutating func fetchRemoteRandomNumber(
        completion: @escaping (Result<Int, LimitError>) -> Void
    ) {
        let result: Result<Int, LimitError>
        if count < AsyncRandomGenerator.upperLimit {
            // 生成回数の上限に達するまで数値を生成
            result = .success(Int.random(in: 1...100))
        } else {
            // エラーを返す
            result = .failure(.limitReached)
        }

        count += 1

        // 非同期的に実行(2秒後に実行)
        queue.asyncAfter(deadline: .now() + 2) {
            completion(result)
        }
    }
}

この例では非同期処理をDispatchQueueを使って実装しています。このfetchRemoteRandomNumberを呼び出す側は以下のようになります。

var generator = AsyncRandomGenerator()

(0..<AsyncRandomGenerator.upperLimit + 1).forEach { _ in
    generator.fetchRemoteRandomNumber { result in
        switch result {
        case .success(let number):
            print(number)
        case .failure(let error):
            print("Source of randomness failed: \(error)")
        }
    }
}

print("Waiting on some numbers.")

dispatchMain()
// Waiting on some numbers. 
// 2秒後...
// 29
// 32
// 62
// 44
// 42
// Source of randomness failed: limitReached

その他の実装例

私が実際に使用した例も載せておきます。FirebaseのAuthを使用したサインイン処理の結果にResult型を使用しました。結果はコールバック処理で返しています。

public func credentialSignIn(credential: AuthCredential,completion : @escaping (Result<Bool, Error>) ->  Void ){
    Auth.auth().signIn(with: credential) { (authResult, error) in
        if error == nil {
          if authResult?.user != nil{
              completion(.success(true))
          }
        }else{
            completion(.failure(error!))
        }
    }
}

引用:GitHub/iOS-Login-function-following-MVVM-architecture

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index