There will be times in processing data where you’ll need to retry if a failure is encountered. Networking is a great example here. If a download only partially completes, you can use Combine’s retry operator to retry the download.
If other errors are encountered, including while retrying, you could try catching those errors to keep your app running smoothly. Let’s dive right into an example using retry and catch.
Included in this playground’s Sources folder is a PhotoService.swift file that contains a mechanism to fetch a photo in either low or high quality using a custom publisher. However, when asking for a high quality image, it always fails, so you can experiment with techniques for handling errors.
Make an instance of the PhotoService. Make an example block that calls fetchPhoto on the photoService and subscribe to the pipeline with a sink. In the receivedValue block, list the image and print a message to console.
let photoService = PhotoService()
example(of: "Catching and retrying") {
photoService
.fetchPhoto(quality: .low)
.sink(
receiveCompletion: { print("\($0)") },
receiveValue: { image in
image
print("Got image: \(image)")
}
)
.store(in: &subscriptions)
}
Run this in the playground, and the console shows you the result of the fetch. You can even use the playground to look at the image.
Change the quality from .low to .high and run the playground again. This time the console reports an error. Maybe we just need to try harder - add a call to retry after the fetchPhoto call.
.retry(3)
You get a free retry mechanism for every piece of work in a publisher thanks to the retry operator. Before running this, add a handleEvents call between the fetchPhoto and retry calls
.handleEvents(
receiveSubscription: { _ in print("Trying ...") },
receiveCompletion: {
guard case .failure(let error) = $0 else { return }
print("Got error: \(error)")
}
)
This will help you see when the retries occur. Run the playground. You’ll see the initial try and 3 retries. Update the fetchPhoto call to have an extra argument - failingTimes. This will cause it to fail twice and then succeed on the third try.
.fetchPhoto(quality: .high, failingTimes: 2)
Run the playground again, and this time the high quality version of the image is received!
What if you want to fall back to a default image if the fetch fails? Remove the failingTimes argument from fetchPhoto and add a call to replaceError after the call to retry.
.replaceError(with: UIImage(named: "na.jpg")!)
Run this in the playground and look at the result. Instead of showing an error, it displays the default image.
What if you wanted to try to fallback to the low quality image if the fetch failed? Add a call to catch after the retry but before replaceError(with:)
.catch { error -> PhotoService.Publisher in
print("Failed fetching high quality, falling back to low quality")
return photoService.fetchPhoto(quality: .low)
}
Run this in the playground. As before an initial attempt is followed by 3 failures, but then the catch operator comes in and requests a low quality version of the image.