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

関連記事

20140807-182914.jpg

【アプリ開発】iOS 8とiOS 7でUIAlertController・UIAlertView・UIActionSheetを切り替えて使う方法

@akio0911です。今までお世話になったUIAlertViewとUIActionShee

記事を読む

I20150805-182005.jpg

Swiftにおける関数型のエッセンスを学ぶために「関数プログラミング実践入門」を読み始めた

Swiftの関数型言語的な特徴についての話を各所で見るようになりましたが、自分は関数型言語の

記事を読む

I20160925-163452.jpg

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

UITabBarControllerで、タブバーアイコン非選択時の色を変更する方法を紹介します!

記事を読む

I20150808-104713.jpg

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

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

記事を読む

I20151030-214417.jpg

【開発本】SpriteKitの入門書籍「iPhoneのゲームアプリをつくろう!」

SwiftとSpriteKitでiPhoneゲームアプリを開発するための入門書「iPhone

記事を読む

I20150924-010943.jpg

Swift 2.0で追加されたOptionSetTypeがなかなか便利

Swift 2.0で追加されたOptionSetTypeプロトコルがなかなか便利なので紹介し

記事を読む

20150106-142403.jpg

Auto Layoutをコードで記述してビューを等間隔に並べる【Swift】

Auto Layoutをコードで記述して、ビューを等間隔に並べる方法について紹介します。ちなみに

記事を読む

I20160118-223109.jpg

Swift 2でシングルトンを初期化・呼び出しする方法

Swift 2でシングルトンを実装する方法について解説したいと思います! (※「シングルトンを

記事を読む

I20160130-110811.jpg

【解決】Redundant conformance of ‘NSDate’ to protocol ‘Comparable’エラーが出た時の対処方法

アプリ開発中に「Redundant conformance of 'NSDate' to pro

記事を読む

20141031-150402.jpg

アップルのベータテストサービス「TestFlight」が正式公開

アップルが先週の木曜、ベータテストサービス「TestFlight」を正式に公開しました。 Te

記事を読む

PAGE TOP ↑