Sponsor Link
Result とは何か?
Result は、実行結果について、結果の状態(成功/失敗)と 結果(計算結果)をまとめて扱うことができる enum です。
Swift の enum は、その中に値を保つことができるので、結果状態だけではなく、結果の値を保持することができます。
Result<Success,Error>とは何か
Result<Success, Error> と書くと、正常実行された際の結果 Success と エラーが発生した時の Error を保持するような Result という意味になります。
結果を保持するので、実行結果の状態に応じて、計算結果、エラー情報を取得することができます。
具体的には、Result<Int, Error>という方の Result に対して、以下のような switch 文を使って、結果をチェックすることができます。
testFunc(value: 1) { result in
switch result {
case .success(let value): // value は、Int 型
print(value)
case .failure(let error): // error は、Error 型
print(error)
}
}
Result を返す側では、以下のような書き方になります。
let result: Result
if value == 1 {
result = Result.success(value) // .success を返すときには、結果の値をセットする(この場合は、Int)
} else {
result = Result.failure(.MoreThanOne) // .failure を返すときには、Error の値をセットする(この場合は、MyCustomStringConvertibleError)
}
Result を使う利点とは?
実行結果状態だけではなく、結果も合わせて保持できることが1つ目です。
これまでの非同期処理では、引数に正常実行結果と合わせて、エラー情報を渡されて、その2つをチェックすることが多かったと思いますが、この情報を合わせた1つの Result が渡されることで、不整合を防ぎやすくなります。
また、Result の実装に、enum が使用されていることで、タイプチェックしやすくなるとともに、switch 等で判定するときにモレヌケが防止できます。
Playground で使ってみる
Playground 上で以下のコードを実行すると、感じがつかめる気がします。
import Foundation
import Combine
enum MyCustomStringConvertibleError: String, Error, CustomStringConvertible {
case NoError = "No Error"
case NotOne = "invalid Value(not 1)"
var description: String {
return rawValue
}
}
func testFunc(value: Int, completion: (Result) -> Void) {
if value == 1 {
completion(.success(value)) // 計算成功として、.success と合わせて値を返す
} else {
completion(.failure(.NotOne)) // エラー発生で、.failure と合わせて、エラー詳細を返す
}
}
testFunc(value: 1) { result in
switch result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
// -> 1 が表示される
testFunc(value: 2) { result in
switch result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
// -> invalid Value(not1) が表示される
Result 応用編:非同期処理 の UI に使うケース
UI として、処理がうまくいったとき と うまくいかなかった時で 表示を切り替えることはよくあります。
Result で結果を保持することで、Result の .success, .failure の状態を表示の切り替えに使うことができます。
さらには、非同期の処理が使われていると、処理が正常終了した、処理が異常終了した 以外に、計算途中という状態も持ち得ます。
Result を ?(optional) という形で保持することで、計算途中という状態を持たせることも可能となります。
//
// ContentView.swift
// Result
//
// Created by Tomoaki Yagishita on 2020/10/17.
//
import SwiftUI
// (1)
enum MyCustomStringConvertibleError: String, Error, CustomStringConvertible {
case NoError = "No Error"
case MoreThanOne = "more than One"
case LessThanOne = "less than One"
var description: String {
return rawValue
}
}
// (2)
struct ContentView: View {
@State private var intValue = 5
var body: some View {
NavigationView{
VStack {
Stepper("value: \(intValue)", value: $intValue)
// (3)
NavigationLink("calc (but it will finish after a while) !", destination: ChildView(intValue: intValue))
}
}
}
}
//(4)
struct ChildView: View {
var intValue: Int
// (5)
@State private var calcResult: Result?
var body: some View {
// (6)
switch calcResult {
case .success(let int):
// (7)
Text("Value is \(int)")
case .failure(let error):
// (8)
Text("Error happened: \(error.description)")
case nil:
// (9)
ProgressView()
.onAppear(perform: {
testFunc(value: intValue) { result in
calcResult = result
}})
}
}
func testFunc(value: Int, completion: @escaping (Result) -> Void) {
// (10)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
let result: Result
// (11)
if value == 1 {
result = Result.success(value)
} else if value > 1 {
result = .failure(.MoreThanOne)
} else {
result = .failure(.LessThanOne)
}
completion(result)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
(1) エラー原因情報を含む MyuCustomStringConvertibleError を定義
(2) 起動時の画面を定義。値設定のスライダーと下位ビューへ遷移するためのボタンを表示
(3) 押下されるとステッパーで指定された値を下位ビューへ渡して遷移する
(4) NavigationLink で遷移する下位ビュー
(5) 非同期を想定した関数の実行結果を保持する Result の定義
(6) 保持している Result の状態によって、表示を切り替える switch 文
(7) Result が .success の状態であれば、結果を Text ビューで表示
(8) Result が .failure の状態であれば、エラーの詳細情報を Text ビューで表示
(9) Result が nil であれば、まだ計算が終了していないので、ProgressView を表示
ChildView へ遷移したときには、Result は nil なので、.onAppear を使用して、ChildView 遷移時に関数実行開始
(10) 実行される関数。非同期を想定しているので、3秒待ってから completion を呼ぶ
(11) 渡された値が 1 であれば、成功として 1 を返し、そうでなければ、”1 より大なのでエラー”、”1 より小なのでエラー”という情報を付与してエラーを返す
まとめ
- Result を使うと、実行状態(成功/失敗)と合わせて実行結果を渡すことができる
- Result を生成するときには、.success と合わせて実行結果値、.failure と合わせて Error をセットする
- 受け取った Result をチェックするときには、.success, .failure をチェックする。抜けもれは、Swift 側でチェックしてくれる
- SwiftUI と組み合わせるときには、Result? としてオプショナルにすると、非同期処理と相性が良い
Swift 学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Swift ポケットリファレンス
Swift を学んでも、プログラミング言語の文法を全て記憶しておくことは無理なので、ちょっとした文法の確認をするために、リファレンス本を手元に置いておくと便利です。
Swift4 までしか対応していないので、相違点を理解して参照する必要があります。
説明は以上です。
不明な点やおかしな点ありましたら、ご連絡いただけるとありがたいです。
Sponsor Link