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コード)

関連記事

I20150909-225806.jpg

Functional Programming in Swiftを読むために、すごいH本を読み終えた感想

「Functional Programming in Swift」という本がありまして、

記事を読む

I20160525-233821.jpg

SwiftBondを使ってみた。observeやObservableの使い方など

Swiftバインディングフレームワークである「SwiftBond」を使ってみました!

記事を読む

20150116-114410.jpg

プログラミング未経験者がSwiftの文法を学ぶのにオススメな本!「たのしいSwiftプログラミング」

楽しみにしていたSwiftの入門書「たのしいSwiftプログラミング」を読み終わったので、レビュ

記事を読む

I20160208-152320.jpg

【Swift】privateなタイプメソッド作るなら、privateな関数でも良いかもと思った

structとかclassの中で、ちょっとした処理を担当させるprivateなタイプ

記事を読む

I20151031-175009.jpg

初心者にオススメの本!Swiftではじめる iPhoneアプリ開発の教科書【Swift 2&Xcode 7対応】

     Xcode 7とSwift 2に対応したアプリ開発の入門書籍「Swiftでは

記事を読む

I20150808-104713.jpg

【Xcode】シミュレーターリストの表示がおかしくなった時の解決方法

Xcodeのシミュレーターリストの表示が、上のスクリーンショットのようにおかしくなってしまっ

記事を読む

20160309-131202.jpg

ゆるい入門書では物足りない人向けの濃い内容!「本気ではじめるiPhoneアプリ作り」

ヤフー株式会社のiOSエンジニアである西 磨翁(にし まお)さんより、「本気ではじめるiPhon

記事を読む

エディタのショートカット・Auto Layout・ブレークポイントに関する詳しい解説も!「Xcode5徹底解説」

著者の@es_kumagaiさんより献本御礼。iOSアプリ開発に用いるツール「Xcode 5」の

記事を読む

I20151119-165939.jpg

Swift 2.1に対応した本「詳解 Swift 改訂版」が出るぞ!

Swiftの文法に関する解説書の定番「詳解 Swift」の、Swift2.1に対応した改訂版「詳

記事を読む

20160730-172416.jpg

Auto Layoutで非表示にした部分を詰める(トルツメ)する方法

Auto Layoutを使って非表示にしたコントロールの部分を詰める(トルツメ)方法を説明します

記事を読む

I20170228-002742.jpg
約3ヶ月で体脂肪率を6.2%も落とせたキッカケについて

(右上の赤枠内がダイエット開始前、左下の赤枠内が3ヶ月後の数値

I20161224-174949.jpg
「季節の野菜を直接配送!季節のスムージー」を買ってみた

「FiNCモール」で、「季節の野菜を直接配送!季節のスムージー」を

I20161002-152537.jpg
【メンズネイル】東京・新宿のネイルサロンでマットネイルしてもらった

2016年7月18日、東京・新宿のメンズOKなネイルサロン「Tot

I20160925-163452.jpg
タブバーアイコン非選択時の色を変更する方法【iOS 10】

UITabBarControllerで、タブバーアイコン非選択時の

I20160924-123726.jpg
iPad Pro 9.7インチ SIMフリーモデルにFREETELのSIMを入れて使ってみた

今までiPad miniで使っていたFREETELのSIM

→もっと見る

PAGE TOP ↑