最佳实践:Combine框架流存在多种Error类型时的错误处理方案
Combine 框架下 Repository 层错误类型设计最佳实践
你当前使用的MVVM架构分层如下:
针对错误类型设计的问题没有绝对最优解,完全取决于上层消费方对错误的处理粒度需求,你可以从以下几个维度判断选型:
核心考量维度
- 上层错误处理的粒度:如果你的ViewModel/View层只需要区分「网络错误」「本地缓存错误」「权限错误」这类通用分类,不需要感知methodX还是methodY特有的错误,选统一大错误类型即可;如果不同业务方法的错误需要做完全不同的交互(比如methodX抛错要弹出引导去开权限,methodY抛错只需要toast提示),选单方法专属小错误类型更清晰。
- 项目迭代维护成本:如果是中小型项目、Repository方法总数不超过20个,统一大错误类型维护更简单,所有
mapError逻辑统一收敛,不需要每个方法单独定义枚举;如果是大型多模块项目、不同业务线的Repository方法逻辑完全独立,单方法专属错误类型可以避免无关错误枚举值互相干扰,编译期就能保证上层不会处理到当前方法根本不会抛出的错误。 - 错误埋点/上报需求:如果需要统一收集所有Repository层的错误做埋点,统一大错误类型更方便做统一的日志上报逻辑,不需要对每个方法的错误单独做类型判断。
两种方案的具体实现
方案1:统一Repository错误类型(适用90%常规业务场景)
定义一个涵盖所有数据源可能抛出的错误枚举,同时预留通用错误case兜底:
enum RepositoryError: Error { case sourceAError(ErrorA) case sourceBError(ErrorB) case sourceCError(ErrorC) case sourceDError(ErrorD) case unknown(Error) // 兜底未定义的错误类型 }
Combine中使用示例:
func methodX() -> AnyPublisher<DataTypeX, RepositoryError> { sourceA.fetch() .catch { _ in sourceB.fetch() } .catch { _ in sourceC.fetch() } .mapError { error in // 把各个数据源的错误映射为统一枚举 switch error { case let errorA as ErrorA: return .sourceAError(errorA) case let errorB as ErrorB: return .sourceBError(errorB) case let errorC as ErrorC: return .sourceCError(errorC) default: return .unknown(error) } } .eraseToAnyPublisher() }
方案2:单方法专属错误类型(适用强类型要求的大型项目)
为每个方法单独定义错误枚举,只包含当前方法可能抛出的错误,编译期就能保证错误处理的完整性:
// methodX专属错误 enum MethodXError: Error { case sourceAError(ErrorA) case sourceBError(ErrorB) case sourceCError(ErrorC) } // methodY专属错误 enum MethodYError: Error { case sourceAError(ErrorA) case sourceDError(ErrorD) case sourceCError(ErrorC) }
这种方案的优势是上层在处理错误时不会出现无效的case判断,比如处理methodX的错误时不需要考虑sourceD相关的错误,编译器还能提示你是否漏了枚举case处理。
折中推荐方案
如果不想写太多重复的枚举定义,可以把通用的错误case抽成公共的,再用关联枚举或者Error协议来做扩展,既保证复用性也不会出现多余的错误类型。
内容的提问来源于stack exchange,提问作者Ahmed Shendy
相关产品推荐
相关产品推荐

