SwiftTaskで失敗したタスクによって異なるエラー処理を行う方法

公開日: : iPhoneアプリ開発

20160508-170745.jpg

SwiftTaskで複数のタスクを.successでチェーンさせ、失敗したタスクによって異なるエラー処理を行う方法について解説したいと思います。

なお、この方法はSwiftTaskの作者であるYasuhiro Inami(@inamiy)さんに教えて頂きました。ありがとうございます!

    

スポンサード リンク

タスクを作る

まずはいつも通りタスクを作りましょう。task1, task2, task3のエラー型がそれぞれBool, Double, Stringとなっており、統一されていない点に注目です。

func task1() -> Task<(),Int,Bool> {
    return Task<(),Int,Bool>() { 
        (progress, fulfill, reject, configure) in

        fulfill(123)
    }
}
func task2() -> Task<(),Int,Double> {
    return Task<(),Int,Double>() { 
        (progress, fulfill, reject, configure) in

        fulfill(123)
    }
}
func task3() -> Task<(),Int,String> {
    return Task<(),Int,String>() { 
        (progress, fulfill, reject, configure) in

        reject("error3")
    }
}

    

エラーを統一して扱うためのenumを作る

それぞれのタスクのエラーを統一して扱うためのenumを作ります。タスクの数だけcaseを作り、付属型をそれぞれのエラー型にします。

これにより、異なる型をenumで包んで統一的に扱えるようになります。

この時、実はWrappedError.ByTask1は、BoolをWrappedErrorに変換する関数として使用できます。

なお、case Cancelledは外部からのキャンセルを内部的なリジェクトに変換するためのものです。

enum WrappedError
{
    case ByTask1(Bool)
    case ByTask2(Double)
    case ByTask3(String)
    
    /// For external cancellation -> internal rejection conversion.
    case Cancelled
}

    

タスクのエラー型のみを変換する関数を作る

タスクのエラー型のみを変換する関数を作ります。つまりTask<Progress,Value,Error>型を、Task<Progress,Value,WrappedError>に変換するわけです。

この変換にはErrorからWrappedErrorへの変換も必要なので、その変換関数を引数として渡す形になっています。

extension Task
{
    func _mapError(f: Error -> WrappedError) 
        -> Task<Progress, Value, WrappedError>
    {
        return self.failure { 
            error, isCancelled -> Task<Progress, Value, WrappedError> in

            if let error = error {
                return Task<Progress, Value, WrappedError>(error: f(error))
            }
            else {
                // converts external cancellation -> internal rejection
                return Task<Progress, Value, WrappedError>(error: .Cancelled)
            }
        }
    }
}

    

タスクのチェーンを作って実行する

最後の.onで、switchを使ってタスクごとに分岐させ、実際のエラー情報を取り出します。

task1()._mapError(WrappedError.ByTask1)
    .success { _ in
        task2()._mapError(WrappedError.ByTask2)
    }
    .success { _ in
        task3()._mapError(WrappedError.ByTask3)
    }
    .on(failure: { error, isCancelled in
        guard let error = error else { return }
        switch error {
        case let .ByTask1(error1):
            print(error1)
        case let .ByTask2(error2):
            print(error2)
        case let .ByTask3(error3):
            print(error3)
        default:
            ()
        }
    })

    

タスクのエラー型だけを置き換えるヘルパー関数を作る

やりたいことは上記の方法で実現できたのですが、タスクをチェーンさせている箇所で毎回._mapError(WrappedError.ByTask1)などを呼び出すのが面倒です。

そこでタスクをチェーンさせる前に、タスクのエラー型のみ置き換えるヘルパー関数を作りました。

なお、SwiftTaskのタスクはインスタンス化された時点で実行開始されるので、変換前のタスクをクロージャとして受け取り、変換後のタスクもクロージャで包むことによって、実際に必要とされるタイミングまでインスタンス化されないようにしています。

この関数の作成にあたっては、熊谷 友宏(@es_kumagai)さんからアドバイスを頂きました。ありがとうございました!

func wrappedErrorTask<P,V,E>(task: () -> Task<P,V,E>, f: E -> WrappedError) 
    -> () -> Task<P,V,WrappedError> {

    return {
        task()._mapError(f)
    }
}

    

このヘルパー関数によって事前にタスクを変換しておけば、タスクをチェーンさせるコードをシンプルに書くことができます。

let wrapped1 = wrappedErrorTask(task1, f: WrappedError.ByTask1)
let wrapped2 = wrappedErrorTask(task2, f: WrappedError.ByTask2)
let wrapped3 = wrappedErrorTask(task3, f: WrappedError.ByTask3)

wrapped1()
    .success { _ in
        wrapped2()
    }
    .success { _ in
        wrapped3()
    }
    .on(failure: { error, isCancelled in
        guard let error = error else { return }
        switch error {
        case let .ByTask1(error1):
            print(error1)
        case let .ByTask2(error2):
            print(error2)
        case let .ByTask3(error3):
            print(error3)
        default:
            ()
        }
    })

    

サンプルはこちら

今回紹介したテクニックを使ったコードをGitHubに置いているので、よかったらチェックしてみてください ⇒ [Test] Add wrappedErrorTask · akio0911/SwiftTask@902335d

    

@akio0911はこう思った。

enumのcaseが関数として使えることや、インスタンス化のタイミングを遅らせるためにクロージャで包み込むテクニックはとても勉強になりました。

Yasuhiro Inami(@inamiy)さんさん、熊谷 友宏(@es_kumagai)さん、ありがとうございました!

    

この記事が気に入ったら「いいね!」しよう

follow us in feedly

Feedlyで最新記事を購読

Twitterで更新情報をゲット!

LINEでご感想・ご要望お送りください!
(スマホでLINEを起動 > 友だち追加 > QRコード)

関連記事

Auto LayoutやStoryboardの仕組みをしっかり理解できる!「UIKit徹底解説」

インプレスジャパン様より献本御礼。UIKit周りを中心に、「Auto Layout」「Story

記事を読む

20150108-113233.jpg

【Swift】コードをコンパクトに記述できるオプショナルチェーン(optional chaining)の性質と活用例

最近はSwiftでコーディングすることが多い@akio0911です。今日はSwiftでコード

記事を読む

20150406-121104.jpg

【アプリ開発】選んだ色をUIColorの生成コードに変換できる「Developer Color Picker」

選んだ色をUIColorの生成コードに変換できる「Developer Color Picker」

記事を読む

20160724-154909.jpg

デザインの素人がノンデザイナーズ・デザインブックを読んだら、デザインの原則が結構分かるようになった!

ずっとエンジニアとして働いてきてデザインに関してはまったくの素人な僕ですが、「ノンデザイナーズ・

記事を読む

20141027-113224.jpg

【Swift】「詳解 Objective-C 2.0 第3版」の著者による「詳解Swift」が予約受付中!

「詳解 Objective-C 2.0 第3版」の筆者「荻原 剛志」さんによる新しい技術書「詳細

記事を読む

20160807-113229.jpg

UITableViewのリサイズに合わせて一番下のセルを常に一番下に表示する方法

一番下のセルを表示している状態でUITableViewの下端を上に移動させると一番下のセルが隠れ

記事を読む

I20160210-140020.jpg

【Xcode】モーダル表示で遷移先へ値を渡す時のハマりポイント

モーダル表示で遷移先の画面へ値を渡す際、最初はハマりやすいポイントについて解説したいと思いま

記事を読む

20140901-120049

デザイン初心者な僕がデザイナーさんにそこそこ褒められるアプリアイコンを作った方法

デザイン初心者の僕がアプリアイコンを作ったら、本職のデザイナーさんにそこそこ褒められるという嬉しい体

記事を読む

I20160411-122213.jpg

Swift 2.2の変更点をまとめてみた

2016年3月22日、Xcode 7.3と共にSwift 2.2が正式にリリースされました。

記事を読む

20150522-105524.jpg

【書籍】初心者にオススメな本!「これからはじめる Apple Watchアプリ開発入門」

Apple Watchは買いましたか?使ってますか?アプリを作ってみたくはないですか? という

記事を読む

【HomeKit】家の照明をSiriで制御できるようにしてみた

Apple製品と家電を連携するシステム「HomeKit(ホーム

【メンズネイル】新宿区大久保のサロンでターコイズとピンクのメタルネイルにしてもらった

2016年10月22日、いつもお世話になっているネイルサロン「

[iOS][Swift]スタックトレース(コールスタック)を表示する2つの方法

iOSアプリ開発で、スタックトレース(コールスタック)を表

【使い方】iOS 10のSiriで出来る事299個をまとめてみた

そろそろiOS 11がリリースされそうな雰囲気ではありますが、iO

【メンズネイル】新宿区大久保のサロンでハーレイ・クインがテーマのネイルにしてもらった

2016年9月27日、いつもお世話になっている「Total b

→もっと見る

PAGE TOP ↑