Swift Combine框架setFailureType操作符疑问:为何sink接收的错误类型不是自定义IntegerError?
Error in Combine's sink Awesome question — this is a super common gotcha with Combine’s strict type system, especially when mixing setFailureType and error-throwing operators like tryMap. Let’s break down why this happens and how you can fix it.
The Root Cause: tryMap Changes the Failure Type
Your setFailureType(to: IntegerError.self) call works exactly as intended: it converts the upstream array publisher (which originally has a failure type of Never, since it can’t fail) to use your custom IntegerError. But here’s the catch: the tryMap operator overrides this failure type.
Take a look at tryMap’s signature:
func tryMap<T>(_ transform: @escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>
The Publishers.TryMap type has a Failure type of Error, not the failure type of your upstream publisher. This is because tryMap allows you to throw any Error instance (not just your custom IntegerError), so Combine widens the failure type to the most general possible case: Error.
That’s why when you reach the sink closure, the error in the .failure case is typed as Error instead of your custom enum — the tryMap operator effectively "upgrades" the failure type, erasing your custom type information.
How to Keep Your Custom Error Type
If you want to avoid manual casting and keep the failure type as IntegerError, you have two straightforward options:
Option 1: Replace tryMap with an Explicit FlatMap Approach
Instead of using tryMap (which opens the door to any error), use flatMap to return either a successful publisher or a failure publisher with your custom error:
enum IntegerError: String, Error { case multipleOf2 = "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 subscription = integerPublisher .flatMap { intValue -> AnyPublisher<Int, IntegerError> in if intValue.isMultiple(of: 2) { // Return a failure publisher with your custom error return Fail(error: IntegerError.multipleOf2).eraseToAnyPublisher() } else { // Return a successful publisher matching the required failure type return Just(intValue) .setFailureType(to: IntegerError.self) .eraseToAnyPublisher() } } .sink { completion in switch completion { case .finished: print("success") case .failure(let error): // No casting needed! Error is already IntegerError print(error.rawValue) } } receiveValue: { value in print(value) }
Option 2: Use mapError to Narrow Back to Your Custom Error
If you prefer to stick with tryMap, add a mapError operator to convert the general Error type back to your custom enum (just ensure you only throw IntegerError in tryMap to make the cast safe):
let subscription = integerPublisher .tryMap { intValue in if intValue.isMultiple(of: 2) { throw IntegerError.multipleOf2 } else { return intValue } } .mapError { error -> IntegerError in // Safe cast since we only throw IntegerError in tryMap error as! IntegerError } .sink { completion in switch completion { case .finished: print("success") case .failure(let error): print(error.rawValue) } } receiveValue: { value in print(value) }
Quick Recap
setFailureTypecorrectly sets the upstream failure type, buttryMapoverrides it toErrorbecause it supports throwing any error.- To preserve your custom error type, either avoid
tryMap(use flatMap with explicit success/failure publishers) or usemapErrorto convert the general error back to your enum.
内容的提问来源于stack exchange,提问作者Kuba Gawecki

