Swift Error重构优化详解
背景现状
项目每积累到一定程度,代码的重构优化是必经之路。
试卷项目初期,整体错误Code较少,直接使用更便于处理错误状态,因此便全部归整到一个单独的 NetWorkError.ResponseCodeType 中,但是随着项目功能的丰富,各个功能模块越来越多,模块错误的处理也各不相同,每个模块都关联了所有的错误Code,后续还会持续增长,导致越来越难以维护。
enum ResponseCodeType: Int { case success = 0 case tokenExpire = 11001 case overVerifyCode = 11011 case verifyCodeExpire = 11002 case verifyCodeIncorrect = 11003 case autoLoginFailed = 11004 case appidLoginFailed = 11005 case phoneIsRegisted = 11006 case phoneHasBinded = 11010 case joinedBeePlan = 11100002 case uploadRepeate = 11020005 case wechatHasBinded = 11010017 case phoneHasBindedOtherWeChat = 11010022 case todayIsSignIned = 11140003 case subjectCountLimit = 11150004 case invalidTagName = 11160002 case alreadyExistsTagName = 11160003 case outOfMaxTagsCount = 11160004 case notRegisterHomework = 11010033 case notSupportNumber = 11010028 case wrongTeamCode = 11210005 case classNotFound = 11210006 case nicknameExists = 11210007 case joinClassThreeTimes = 11210008 case identityNickNameExists = 11210014 case checkClassCodeMax = 11210016 case createClassMAx = 11210015 case joinTeamMax = 11210017 case studentCountMax = 11210018 case other = -99999 }
问题分析
提前分析、明确目标。
期望结果
- 错误处理分为两部分:通用、自定义模块,二者各自处理
- 拓展性强,各个模块可自定义并处理错误,基类代码保持稳定不变
- 支持点语法、穷举校验,使用清晰便捷
技术选型
根据期望结果,可以大致选定技术方向
- 拓展性:泛型、协议
- 类型穷举:枚举
优化解决
前后对比,不断调优。
Error模型
- 区分通用和自定义模块
- 将 ResponseCodeType 降为通用Code类型,可以将其类型固定
- 替换 NetWorkError,使用 ModuleRespError 作为基类Error,通过泛型为外部模块提供自定义能力
优化前
struct NetWorkError: Error { var code: ResponseCodeType = .other var msg: String { code.errorString } }
优化后
/// 错误类型描述 public protocol ISErrorProtocol { var errorString: String { get } } public enum ModuleRespError<T: ISErrorProtocol>: Error { /// 对应模块自定义类型code case type(_ value: T) /// 基类请求code case baseType(_ value: ResponseCodeType) /// 错误提示归整 public var mapErrorString: String { switch self { case .type(let value): return value.errorString case .baseType(let value): return value.errorString } } }
基类Request
使用协议的类型占位符 associatedtype,便于后续进行 rawValue 的枚举映射
- 分层处理错误类型,基类错误放到基类请求的回调中处理,抛出模块的错误code
在ISTargetType协议中关联错误码类型 associatedtype ErrorCodeType: RawRepresentable
public protocol ISTargetType { /// 错误码类型,由各模块自定义 associatedtype ErrorCodeType: RawRepresentable }
优化前
/// 根据 ISTargetType 枚举类型调用接口,返回 model static func requestISType<T: ISTargetType>(_ server: T, completion: @escaping (_ model: NetworkModelResponse?, _ code: ResponseCodeType) -> Void) { // ... Network.IS.fetchDataDic(server) { dataDic in guard let dataDic = dataDic, let model: NetWorkResponseModel = NetWorkResponseModel.deserialize(from: dataDic) else { completion(nil, .other) return } // 判断code 是否为token过期 let codeValue = model.ret ?? ResponseCodeType.other.rawValue // errorType let codeType = ResponseCodeType(rawValue: codeValue) ?? .other // 基类Code处理,token过期 NetWorkRequest.checkTokenDidExpire(codeType) // 抛出的code:基类、模块混在一起 completion(model, codeType) } }
优化后
/// T.ErrorCodeType: 遵循 RawRepresentable 协议的泛型 /// Result<Success, Failure> 拆分成功、失败逻辑 static func requestISResultType<T: ISTargetType>(_ server: T, result: @escaping ((Result<NetWorkResponseModel, ModuleRespError<T.ErrorCodeType>>) -> Void)) { // ... Network.IS.fetchDataDic(server) { dataDic in // 接口数据处理 guard let dataDic = dataDic, let model: NetWorkResponseModel = NetWorkResponseModel.deserialize(from: dataDic), let retCode = model.ret else { // 接口错误,默认基类错误 let error: ModuleRespError<T.ErrorCodeType> = .baseType(.other) result(.failure(error)) return } if retCode == 0 { // 成功返回 result(.success(model)) return } // 请求失败 if let baseType = ResponseCodeType(rawValue: retCode) { result(.failure(.baseType(baseType))) // 优先处理基类错误code,例如 token失效 NetWorkRequest.checkTokenDidExpire(baseType) } else if let retValue = retCode as? T.ErrorCodeType.RawValue, let moduleType = T.ErrorCodeType(rawValue: retValue) { // 解析并返回模块错误码 result(.failure(.type(moduleType))) } } }
模块调用
- 各模块自定义ErrorCode,互不干涉
- 通过泛型参数定义ErrorCode类型
- 使用Result<Success, Failure>,消除结果可选值,成功失败二选一,区分处理
- 限制失败Error类型,仅需处理当前模块和基础错误,无需关注其他类型错误
优化前
public func queryDemo(with params: [String: String], completionHandler: @escaping (_ model: DemoModel?, _ code: ResponseCodeType) -> Void) { NetWorkRequest.requestISType(GroupQueryServer.createGroup(params)) { model in // ... let code = model.ret ?? -1 let type = ResponseCodeType(rawValue: code) ?? .other guard type == .success, let result = DemoModel.deserialize(from: model.data) else { completionHandler(nil, type) return } completionHandler(.success(resultModel)) } }
logic.queryDemo(with: params) { model, code in // 只能通过解包model来判断接口的成功或失败 guard let model = model else { // 失败处理 handleFail(code: code) return } // 成功处理 hanldeSuccess() } private func handleFail(code: ResponseCodeType) { // ... // 当前模块错误处理 let showWarning = code == .wrongTeamCode || code == .classNotFound // UI处理 warningLabel.isHidden = !showWarning // 提示 CEProgressHUD.showTextHUD(code.errorString) }
优化后
public enum StudyGroupRespCode: Int, ISErrorProtocol { case wrongTeamCode = 11210005 case classNotFound = 11210006 case nicknameExists = 11210007 case joinClassThreeTimes = 11210008 case identityNickNameExists = 11210014 case checkClassCodeMax = 11210016 case createClassMAx = 11210015 case joinTeamMax = 11210017 case studentCountMax = 11210018 case folderLevelLimit = 11210027 case curIdentifierError = 11210011 case clockFrequencyInvalid = 11210036 case other }
public func queryDemo(with params: [String: String], completionHandler: @escaping ((Result<ClassItemModel, ModuleRespError<StudyGroupRespCode>>) -> Void)) { // 基类请求 NetWorkRequest.requestISResultType(GroupQueryServer.createGroup(params)) { result in switch result { case .success(let success): // 结果处理que if let resultModel = ClassItemModel.deserialize(from: success.data) { // 转换模块模型model completionHandler(.success(resultModel)) } else { // 转化失败,默认other completionHandler(.failure(.type(.other))) } case .failure(let error): // 抛出的模块错误 completionHandler(.failure(error)) } }
logic.queryDemo(with: params) { result in // 通过 Result 划分结果状态 switch result { case .success(let model): // 成功处理 hanldeSuccess() case .failure(let error): // 失败处理 handleError(error) } } // 示例为简单处理,若需精细化处理错误,拆分优化后的代码,逻辑明显更加清晰 private func handleError(_ error: ModuleRespError<StudyGroupRespCode>) { switch error { case .type(let code): // ... // 当前模块错误处理 let showWarning = code == .wrongTeamCode || code == .classNotFound // UI处理 warningLabel.isHidden = !showWarning // 提示 CEProgressHUD.showTextHUD(code.errorString) case .baseType(let error): // 基类错误处理 CEProgressHUD.showTextHUD(error.errorString) } }
总结
至此,我们已经了解了有关ErrorCode的重构优化的大体逻辑,从后续的开发流程结果可以看出,确实对项目的Code混乱增长有了良好的控制,各模块只需要关注处理自己的异常code,降低了维护代码难度,后续也会持续关注和优化。
参考资料
- Result 还是 Result<T, E: Error>
以上就是Swift Error重构优化详解的详细内容,更多关于Swift Error重构优化的资料请关注编程宝库其它相关文章!
背景知识Cocoa API 中有很多接受回调的异步方法,比如URLSession的dataTask(with:comp ...