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

関連記事

I20160505-011515.jpg

iOS 9以降で使えるReadable Content Guideを試してみた

iOS 9から追加されたReadable Content Guide(readableConte

記事を読む

iPhoneアプリ開発の初心者にオススメな本はこれだ!iOS 7とXcode 5に対応!

僕が講師をしている初心者向けのiPhoneアプリ開発講座「アプリクリエイター道場」でも参考書とし

記事を読む

20141027-113224.jpg

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

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

記事を読む

I20160119-155700.jpg

Xcodeで80文字目のところにガイドラインを表示する方法

Xcodeで80文字目のところに線(ガイドライン)を表示する方法について解説します! 「ソース

記事を読む

20150410-122409.jpg

複数バージョンのXcodeを共存してMacにインストールする方法

いよいよXcode 6.3が正式にリリースされましたね。 今回は複数バージョンのXco

記事を読む

20140901-120049

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

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

記事を読む

20150409-103620.jpg

描いたベクターグラフィックをSwift/Objective-Cコードに変換できる「Paint Code 2」

引用元 : PaintCode ベクターグラフィックを描き、それを描画するためのSwift

記事を読む

20141224-231137.jpg

【Swift】Auto Layoutで特定のデバイス・画面サイズの時だけ制約を変更する方法

Auto Layoutで、特定のデバイスや画面サイズの時だけ制約を変更する方法について紹介したい

記事を読む

20140916-130550

Swiftの列挙型、switch文、網羅性チェックが素晴らしい!

アップルの新プログラミング言語「Swift」をちょっとずついじってるんですが、列挙型とswitch文

記事を読む

20160509-200435

【Auto Layout】readableContentGuideを使わずに最大幅を定義する

iOS 9からUIViewにreadableContentGuideが追加されましたが、re

記事を読む

20170423-182541
良肌研究室のフェイスウォッシュとオールインワンジェルを使ってみた

株式会社ブラシナさんから、良肌研究室の商品をご提供頂いたので、

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で、タブバーアイコン非選択時の

→もっと見る

PAGE TOP ↑