You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Combine框架setFailureType操作符疑问:为何sink接收的错误类型不是自定义IntegerError?

Why Your Custom Error Type Gets Widened to 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

  • setFailureType correctly sets the upstream failure type, but tryMap overrides it to Error because it supports throwing any error.
  • To preserve your custom error type, either avoid tryMap (use flatMap with explicit success/failure publishers) or use mapError to convert the general error back to your enum.

内容的提问来源于stack exchange,提问作者Kuba Gawecki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 16:22:28