【Swift/Combine】retryメソッドの使い方!特定のエラー発生で再度実行する方法

この記事からわかること

  • SwiftCombineフレームワーク
  • retryメソッド使い方
  • エラー発生の場合に再試行する方法
  • 特定のエラー発生の場合のみ再試行させるには?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

公式リファレンス:Combine Framework

環境

retryメソッドの使い方

公式リファレンス:retryメソッド

func retry(_ retries: Int) -> Publishers.Retry<Self>

SwiftのCombineフレームワークのretryエラーが発生した際再度パブリッシャーを実行(再試行)するためのメソッドです。引数には再試行を行いたい回数Int型で渡します。

let publisher = PassthroughSubject<Int, MyError>()

enum MyError: Error {
    case someError1
    case someError2
}

let subscription = publisher
    .catch { error -> AnyPublisher<Int, MyError> in
        print("エラー発生")
        return Fail<Int, MyError>(error: error).eraseToAnyPublisher()
    }
    .retry(1)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("完了")
        case .failure(let error):
            print("Error:", error)
        }
    }, receiveValue: { value in
        print("Value:", value)
    })

publisher.send(completion: .failure(.someError1))

エラーの発生が分かりやすいようにcatchメソッドを使用して検知する度に出力してみると以下のようになります。sendメソッドでエラーを送っているのは1回ですが2回「エラー発生」が出力されていることがわかります。

おすすめ記事:【Swift/Combine】catchメソッドの使い方!エラーを補足する方法

エラー発生
エラー発生
Error: someError1

エラーが発生した場合その位置で一旦終了してpublisherの再試行が走るのでretrycatchの上に実装した場合は「エラー発生」は1回しか出力されません。

let subscription = publisher
    .retry(1)
    .catch { error -> AnyPublisher<Int, MyError> in
        print("エラー発生")
        return Fail<Int, MyError>(error: error).eraseToAnyPublisher()
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("完了")
        case .failure(let error):
            print("Error:", error)
        }
    }, receiveValue: { value in
        print("Value:", value)
    })
エラー発生
Error: someError1

特定のエラーが発生した場合のみ再試行する

Combineに定義されているretryメソッドは先ほどのみで引数に渡せるのは再試行回数だけとなっています。ですが「特定のエラーが発生した場合のみ再試行」といった要件を満たしたい場合は以下の記事で紹介されている、カスタムでオペレーターを実装することで対応することができるようです。

参考文献:Swift combine retry only for some error types


extension Publishers {
    struct RetryIf<P: Publisher>: Publisher {
        typealias Output = P.Output
        typealias Failure = P.Failure
        
        let publisher: P
        let times: Int
        let condition: (P.Failure) -> Bool
                
        func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            guard times > 0 else { return publisher.receive(subscriber: subscriber) }
            
            publisher.catch { (error: P.Failure) -> AnyPublisher<Output, Failure> in
                if condition(error)  {
                    return RetryIf(publisher: publisher, times: times - 1, condition: condition).eraseToAnyPublisher()
                } else {
                    return Fail(error: error).eraseToAnyPublisher()
                }
            }.receive(subscriber: subscriber)
        }
    }
}

extension Publisher {
    func retry(times: Int, if condition: @escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
        Publishers.RetryIf(publisher: self, times: times, condition: condition)
    }
}

使用する場合は以下のように使用できるようです。

let subscription = publisher
    .catch { error -> AnyPublisher<Int, MyError> in
        print("エラー発生")
        return Fail<Int, MyError>(error: error).eraseToAnyPublisher()
    }
    .retry(times: 4) { error in
        switch error { 
        case MyError.someError1:
            return true
        default:
            return false
        }
    }
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("完了")
        case .failure(let error):
            print("Error:", error)
        }
    }, receiveValue: { value in
        print("Value:", value)
    })

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index