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

関連記事

I20150714-010114.jpg

【Swift】Objective-Cで書いたクラスをメソッド単位で少しずつSwiftへ移行する方法

Objective-Cで作った既存のアプリをSwiftで書き直したいと思っているのですが、一

記事を読む

I20151201-120520.jpg

【Swift 2】switch内にてguardでbreakする

Swift 2のguard内ではreturnしないと文法エラーとなりますが、実はswitch

記事を読む

20140901-120049

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

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

記事を読む

20141227-125250.jpg

実践的な内容まで載っている!開発のプロが教える Swift標準ガイドブック

株式会社マイナビ様より「開発のプロが教える Swift標準ガイドブック」の見本紙を頂きました。早

記事を読む

I20150907-185322.jpg

uGUIの使い方が分かる本「uGUIではじめるUnity UIデザインの教科書」

株式会社マイナビ様より献本御礼。 本書は、公式のUIフレームワーク「Unity UI(uGUI

記事を読む

20160807-113229.jpg

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

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

記事を読む

20160509-200435

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

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

記事を読む

20141210-164612.jpg

【Swift】Generated Header内にSwiftクラスの定義が記述されない場合の確認事項【Xcode6】

Swiftで書いたクラスをObjective-Cで使う際には、「Generated Header

記事を読む

I20160102-131507.jpg

Swift 2の文法が分かるオススメ本「詳解 Swift 改訂版」

Swift文法書の定番とも言える、荻原 剛志さんの「詳解 Swift」。 Swiftの文法をキ

記事を読む

20150410-122409.jpg

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

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

記事を読む

I20170521-225453.jpg
東京駅八重洲口の「羊肉酒場 悟大」で網焼きジンギスカンを頂きました!

ゆうせいさんと株式会社 大庄さんからご招待頂き、悟大withサッポ

I20170514-165235.jpg
iPhoneと連携できる体重体組成計「Withings Body Cardio」を使ってます

ジムに通い始めて体脂肪率が落ち始めたのをキッカケに、iPhoneと

I20170507-155440.jpg
【派手髪】ハーレイクイン風の髪色に染めてもらいました

2016年10月20日、ハーレイクイン風の髪色に染めてもらいました

I20170504-173110.jpg
【メンズネイル】東京・新宿のネイルサロンでターコイズのホログラムネイルしてもらった

ネイルネタが1年分くらい溜まっているので、ちょっとずつ書いていこう

I20170502-010117.jpg
SNUGGのライトニングケーブルが耐久性高し。8ヶ月使ってますが断線の気配なし!

iPhoneの充電&転送ケーブルであるLightningケーブルっ

→もっと見る

PAGE TOP ↑