A smarter Retry with RxSwiftExt

Sometimes your observable will fail with an error but that would not mean that you need to give up trying. For example saving to a file might fail because the file is locked temporarily but a split second later would be unlocked and ready for your changes. Same goes for web requests - there might be a temporary glitch in connectivity which could make the observable fail.

RxSwift offers a special operator called retry, which allows you to try another time the observable subscription upon error.

You can even tell retry how many times to keep trying, which is very useful. The code looks like this:

responseObservable.retry(3)
  .subscribe(onNext: {next in
    print("next: \(next)")
  }, onError: { error in
    print("error: ")
  })

In case responseObservable fails, the subscription will “restart” up to 3 times and in case it fails all 3 times, it will error out.

As useful as the built-in operator is - you rarely can solve a real problem by retrying immediately.

Especially in problems caused by lack of connectivity (and others of course) it’s much more useful to wait a little and then retry, hoping that meanwhile the problem was resolved.

Sometimes it even makes sense to first wait a short interval of time, and after few fails wait longer and longer before retrying.

There is no built-in retry operator that will allow you to do that, but you could give a try to RxSwiftExt - an extra set of operators developed by the community (maintainer of the library is fpillet ).

You can have a look at all of the operators included in RxSwiftExt in its README: https://github.com/RxSwiftCommunity/RxSwiftExt.

After you include (via CocoaPods or Carthage) RxSwiftExt in your project you have few options how to use the smarter retry operator.

The smarter retry takes in a parameter of the RepeatBehavior enum, which can be one of these four types:

  • immediate (maxCount:) - similar to the built-in behavior
  • delayed (maxCount:time:) - retries up to maxCount times, but with time amount of seconds between retries
  • exponentialDelayed (maxCount:initial:multiplier:) - retries up to maxCount times, starts with initial amount of seconds but uses multiplier to increase the delay between retries.
  • customTimerDelayed(max:delayCalculator:) - retries up to max times. delayCalculator is a closure that gets as input the number of retries so far, and returns how much seconds to wait before trying again.

As an extra functionality you can add a closure that evaluates on each retry whether the operator shoud keep trying at all.

Retrying at equal intervals

responseObservable.retry(.delayed(maxCount: 3, time: 5.0),
 shouldRetry: {error in
  return 50..<80 ~= (error as NSError).code
})

This is a more elaborate example so let’s have a look. If responseObservable emits an error retry will try up to 3 times, with 5 second intervals between the retries.

Additionally between retries retry will consult with the shouldRetry closure if it should keep going. Once the code casts error as an NSError, it checks the code of the error. It lets retry keep trying if the error code is between 50 and 80, and stops retrying if the code is not in that range.

In your own app return true or false depending on your business logic.

Retrying in increasing intervals

The formula calculting the delay between retries is initial * pow(1 + multiplier, Double(currentRepetition - 1)).

Let’s have a look at an example:

retry(.exponentialDelayed(maxCount: 3, initial: 2.0, multiplier: 1.5))

The delay between first and second tries is 2.0 seconds (the value of initial parameter). Then between second and third tries is 5.0 seconds - 2.0 * 2.5 seconds.

Calculating the delay

In the end - if you go for a custom calculator you can simply return any value based on any kind of custom business logic so sky is pretty much the limit.

As an example here is a piece of code that implements decreasing delays between tries: 10, 9, 8, 7, 6, etc.

let speedUp: (UInt) -> Double = {retries in
  return max(0.0, 10 - Double(retries))
}

responseObservable.retry(.customTimerDelayed(maxCount: 10,
  delayCalculator: speedUp))
    .subscribe()

A non RxSwift retry

As a fun bonus, I’ve made a retry library which doesn’t use RxSwift at all as I find a custom-delay retry very useful in all kinds of apps. You can use the pure Swift retry in a very similar manner: https://github.com/icanzilb/retry

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.