Home > Mobile >  Swift Combine framework setFailureType error operator
Swift Combine framework setFailureType error operator

Time:10-01

For scientific reasons I've created a Publisher and a Subscriber so I can dive into Combine.

The Publisher has been converted from a never failing to the failing one.

enum IntegerError: String, Error {
    case miltupleOf2 = "We are sorry but the number is a multiple of 2, therefore cannot be used in the process"
}

let integerPublisher = [1,3,3,3,3,3,5,6,7,7].publisher
    .setFailureType(to: IntegerError.self)

let subscribtion = integerPublisher
    .tryMap { intValue in
        if intValue.isMultiple(of: 2) {
            throw IntegerError.miltupleOf2
        } else {
            return intValue
        }
    }
    .sink { completion in
        switch completion {
        case .finished:
            print("success")
        case .failure(let error):
            if let error = error as? IntegerError {
                print(error.rawValue)
            } else {
                print(error)
            }
        }
    } receiveValue: { value in
        print(value)
    }

My question is: when using sink, the error type is Error. Why is it not the custom IntegerError that I've used within the .setFailureType modifier?

The need of casting my error to the type that I specified earlier seems a little redundant.

Thank you.

CodePudding user response:

The reason for this is quite straightforward. tryMap returns a Publishers.TryMap<Upstream, Output>, which is a specific kind of publisher with Failure == Error:

typealias Failure = Error

So as soon as you use tryMap, that undoes what setFailureType did.

The reason why Publishers.TryMap has Error as its Failure type is because in Swift, you can't specify what specific type of error a closure can throw (unlike Java for example). Once you mark a closure as throws, like tryMap has done with its transform parameter:

func tryMap<T>(_ transform: @escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>

any Error can be thrown inside the transform closure. You can try changing throw IntegerError.miltupleOf2 to throwing another type of error. Your code will still compile.


Hypothetically, if Swift allowed you to specify what type of error you are allowed to throw in the closure, then tryMap could have been declared as (fake syntax):

func tryMap<T, E: Error>(_ transform: @escaping (Self.Output) throws E -> T) -> Publishers.TryMap<Self, T, E>

and you wouldn't even need setFailureType.


As a workaround, you can use mapError to cast the error to your desired type:

let subscribtion = integerPublisher
    .tryMap { intValue -> Int in
        if intValue.isMultiple(of: 2) {
            throw IntegerError.miltupleOf2
        } else {
            return intValue
        }
    }.mapError { $0 as! IntegerError }
    .sink { completion in
        switch completion {
        case .finished:
            print("success")
        case .failure(let error):
            print(error.rawValue)
        }
    } receiveValue: { value in
        print(value)
    }

You need to reassure the compiler that you haven't thrown any other type of errors in tryMap.

  • Related