はじめに
エラー処理にはOptional<Wrapped>型による方法やdo catch文による方法などがありますが、今回はAPI通信にて使用されることの多いResult<Success,Failure>型による方法について解説したいと思います。
Result<Success,Failure>型とは
Optional<Wrapped>型によるエラー処理では成功を結果の値で、失敗をnilで表すのに対して、Result<Success,Failure>型では成功を結果の値で、失敗をエラーの詳細で表します。
Result<Success,Failure>型は型引数を2つ取る列挙型で、.successと .failureの2つのケースを持ちます。型引数のSuccessは成功時の値の型を表し、Failureは失敗時のエラーの型を表します。
またSwiftの標準ライブラリにはエラーを表すプロトコルのErrorがあるため、型引数Failureはこのプロトコルに準拠します。
public enum Result<Success, Failure> where Failure: Error {
case success(Success) // .sucessの場合はSuccess型の連想値を持つ
case failure(Failure) // .failureの場合はFailure型の連想値を持つ
}
実装方法
次の例では、ユーザーIDを渡すと、対応するユーザー情報を返すfindUser(byID:)関数を用いて、成功時はその結果を、失敗時は発生したエラーを取得できるようにしています。
失敗時のエラーは列挙型DatabaseErrorとして定義し、データが見つからないエラーを.entryNotFound、重複したデータによるエラーを.duplicatedEntry、不正なデータによるエラーを.invalidErrorとしています。そしてswitch文を用いて、これらのすべてのエラーに対して網羅的にエラー時の動作を実装しています。
enum DataBaseError: Error {
case entryNotFound
case duplicatedEntry
case invalidEntry(reason: String)
}
struct User {
let id: Int
let name: String
let email: String
}
func findUser(byID id: Int) -> Result<User, DataBaseError> {
let users: [User] = [
.init(id: 1, name: "Yusei Nishiyama", email: "nishiyama@example.com"),
.init(id: 2, name: "Yosuke Ishikawa", email: "ishkawa@example.com")
]
for user in users {
if user.id == id {
return .success(user)
}
}
return .failure(.entryNotFound)
}
let id = 0
let result = findUser(byID: id)
switch result {
case let .success(user):
print(".success: \(user)")
case let .failure(error):
switch error {
case .entryNotFound:
print(".failure: .entryNotFound")
case .duplicatedEntry:
print(".failure: .duplicatedEntry")
case .invalidEntry(let reason):
print(".failure: .invalidEntry(\(reason)")
}
}
// 実行結果
// .failure: .entryNotFound
利用するべき時
⑴ エラーの詳細を提供したい時
Result<Success, Failure>型では連想値を通じて失敗時にエラーの値を返します。したがって、エラー発生時には必ずエラーの詳細を受け取ることができ、詳細に応じてエラー処理の挙動を細かくコントロールできます。
またResult<Success, Failure>型ではエラーの型が型引数となっているため、任意のかたでエラーの情報を表現できます。したがって、発生し得るエラーの種類をあらかじめ把握できるというメリットや、エラー処理に必要な情報を受けることができるというメリットがあります。
⑵ 非同期処理のエラーを扱うとき
Result<Success, Failure>型は値として処理の結果を表せるため、関数やクロージャの引数や戻り値に指定できます。クロージャを用いて非同期処理の結果を呼び出し元に通知する場合であれば、呼び出し元にエラー情報も伝えることができます。
一方、Optional<Wrapped>型では値の有無しか表すことしかできず、またdo-catch分ではそもそも発生したエラーを呼び出し元に伝えることができません。その点においてResult型は優れています。
参考
・Swift実践入門 (https://gihyo.jp/book/2020/978-4-297-11213-4)