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
.