欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Swift Error重构优化详解,

来源: 开发者 投稿于  被查看 47447 次 评论:66

Swift Error重构优化详解,


目录
  • 背景现状
  • 问题分析
    • 期望结果
    • 技术选型
  • 优化解决
    • Error模型
    • 优化前
    • 优化后
  • 基类Request
    • 优化前
    • 优化后
  • 模块调用
    • 优化前
    • 优化后
  • 总结

    背景现状

    项目每积累到一定程度,代码的重构优化是必经之路。

    试卷项目初期,整体错误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重构优化的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • SwiftUI开发总结combine原理简单示例详解
    • 仓库模式及其在Swift 项目中的应用详解
    • swift依赖注入和依赖注入容器详解
    • Framework中实现OC和Swift的混编方案
    • Swift设计思想Result<T>与Result<T, E: Error>类型解析
    • Swift Error重构的基础示例详解
    • swift内存管理指针类型使用实例详解
    • Swift中的HTTP请求体Request Bodies使用示例详解

    用户评论