Combinest Error Handling in Swift

Using tryMap, catch, replaceError operations and Fail Publisher

Tolga Taner
3 min readJan 25, 2023
Photo by Sarah Kilian on Unsplash

Error handling provides us catching an error during an application lifecycle in the mobile development. When we develop the app by written imperatively in Swift such as try-catch, Result failure case are significant to catch any error. In Combine, differently, it pushes us to handle catching an error at the beginning of the defining a property. Specifically, Publishers, naturally Subjects have an extra generic error parameter by default, Failure.

Depending on our requirements, we are lucky that there are many options to handle any encountered error using tryMap, replaceError, replaceEmpty, catch. In our example that we will focus on the standard error handling for error cases and we are going to discuss what if we use other options.

Let’s look this example that we have been focusing on loading any local JSON file for mocking all network requests in our app so we will create a protocol that relates the requirement of loading. This is also useful other local purposes like fetching any plist file but the main idea here is fetching all JSON files which we give fileName, extension and an Element type using load().

Before the conformation of LocalLoader to anywhere, for more testable code purpose, we need to define one more protocol so definitely more specific than LocalLoader, named JSONLoaderProtocol.

The whole operation should run on the background queue named operationQueue to prevent UI locking and we just need a JSONDecoder named decoder.

We can easily define a classJSONLoader right now, so we used class because of its reference but struct is also okay. It contains two generic type that we defined V and U. V represents type of our base object that we decode and U represents also type of an object for file name of the json file but the problem here is

Why are they same purpose and why we didn’t use one generic parameter instead of two?

Because, if we use nested generic typed class like <BaseResponse<Response>>, it is tough to access Response here, so we seperated them.

Let’s focus on the error handling aspect. Until now, we didn’t talk about the error handling much, we only created the base. We only defined OperationError enum to specify it.

In load() scope, there is a chain operations here and we created a Just publisher because of a constant element that we need to create the subscription. After that, we rescued from its optional value using compactMap and we transformed it to URL using map before converting it to data. Converting data process needs tryMap we handled by throwing cantLoadContent.

Everything looks great. However, when we called sink() I mean subscribe, to result of load(), we can’t see anything to show an error even if catch closure throws an error in tryMap. We have two problems here, the file name or extension is not compatible with existing JSON file, the data can’t be decoded due to inconsistency between existing JSON file and the data.

To accomplish these, we can use replaceEmpty() after compactMap to handle an empty upstream by replacing a value with same type. We used an empty string for throwing cantLoadContent.

The second one was about our local JSON format. We may decode wrong depends on the corresponding key so we can use catch() and Fail publisher after decode() is called to specify throwing error.

That’s all for now. We handled any error using publishers for our case.

Thanks for reading!