1. 根底介绍 Swift Error(上)
  2. 重构优化 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
}

问题剖析

提早剖析、明晰目标。

希望成果

  1. 过错处理分为两部分:通用、自界说模块,二者各自处理
  2. 拓展性强,各个模块可自界说并处理过错,基类代码保持稳定不变
  3. 支持点语法、穷举校验,运用明晰快捷

技能选型

依据希望成果,能够大致选定技能方向

  1. 拓展性:泛型、协议
  2. 类型穷举:枚举

优化解决

前后对比,不断调优。

Error模型

  1. 区别通用和自界说模块
  2. 将 ResponseCodeType 降为通用Code类型,能够将其类型固定
  3. 替换 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 的枚举映射

  1. 分层处理过错类型,基类过错放到基类恳求的回调中处理,抛出模块的过错code
  2. 在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)))
        }
    }
}

模块调用

  1. 各模块自界说ErrorCode,互不干涉
  2. 经过泛型参数界说ErrorCode类型
  3. 运用Result<Success, Failure>,消除成果可选值,成功失利二选一,区别处理
  4. 限制失利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>