【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つです。
- success:成功
- 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)
}
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(_:) ケースに関連付けられた値を使用して、呼び出しの結果に関する情報を伝えます。
例えば以下は公式に記述されていた「ランダムな数値を非同期的に上限回数まで生成するプログラム」を実装した例です。少し改変して分かりやすくしています。
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
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。