Background status
Whenever the project accumulates to a certain level, the reconstruction and optimization of the code is the only way to go.
In the early stage of the test paper project, there are fewer overall error codes, and it is easier to use directly to handle error status, so it is all sorted into a separateHowever, as the project functions are enriched, each functional module is becoming more and more numerous, and the handling of module errors is also different. Each module is associated with all error codes, which will continue to grow in the future, making it increasingly difficult to maintain.
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 }
Problem analysis
Analyze and clarify goals in advance.
Expected results
- Error processing is divided into two parts: general and custom modules, each of which handles it separately.
- Strong scalability, each module can be customized and handled errors, and the base class code remains stable and unchanged
- Supports point grammar and exhaustive verification, and is clear and convenient to use
Technical selection
According to the desired results, the technical direction can be roughly selected
- Extensibility: generics, protocols
- Type Exhaustion: Enumeration
Optimization solution
Compare the front and back, constantly adjust.
Error Model
- Distinguish between general and custom modules
- ResponseCodeType to a general code type, which can be fixed
- Replace NetWorkError, use ModuleRespError as the base class Error, and provide customization capabilities for external modules through generics.
Before optimization
struct NetWorkError: Error { var code: ResponseCodeType = .other var msg: String { } }
After optimization
/// Error type descriptionpublic protocol ISErrorProtocol { var errorString: String { get } } public enum ModuleRespError<T: ISErrorProtocol>: Error { /// Corresponding module custom type code case type(_ value: T) /// Base class request code case baseType(_ value: ResponseCodeType) /// Error prompt summary public var mapErrorString: String { switch self { case .type(let value): return case .baseType(let value): return } } }
Base class Request
Use the protocol's type placeholder associatedtype to facilitate subsequent enumeration mapping of rawValue
- Process error types in layered, put base class errors into the callback of base class request, throw the module's error code
Associate error code type in ISTargetType protocolassociatedtype ErrorCodeType: RawRepresentable
public protocol ISTargetType { /// Error code type, customized by each module associatedtype ErrorCodeType: RawRepresentable }
Before optimization
/// Call the interface according to the ISTargetType enumeration type, return modelstatic func requestISType<T: ISTargetType>(_ server: T, completion: @escaping (_ model: NetworkModelResponse?, _ code: ResponseCodeType) -> Void) { // ... (server) { dataDic in guard let dataDic = dataDic, let model: NetWorkResponseModel = (from: dataDic) else { completion(nil, .other) return } // Determine whether the code is token expires let codeValue = ?? // errorType let codeType = ResponseCodeType(rawValue: codeValue) ?? .other // Base class Code processing, token expires (codeType) // The thrown code: base class and module are mixed together completion(model, codeType) } }
After optimization
///: Generics that follow the RawRepresentable protocol///Result<Success, Failure> Logic of success and failure of splitstatic func requestISResultType<T: ISTargetType>(_ server: T, result: @escaping ((Result<NetWorkResponseModel, ModuleRespError<>>) -> Void)) { // ... (server) { dataDic in // Interface data processing guard let dataDic = dataDic, let model: NetWorkResponseModel = (from: dataDic), let retCode = else { // Interface error, default base class error let error: ModuleRespError<> = .baseType(.other) result(.failure(error)) return } if retCode == 0 { // Return successfully result(.success(model)) return } // Request failed if let baseType = ResponseCodeType(rawValue: retCode) { result(.failure(.baseType(baseType))) // Priority-to-handle base class error code, such as token failure (baseType) } else if let retValue = retCode as? , let moduleType = (rawValue: retValue) { // parse and return the module error code result(.failure(.type(moduleType))) } } }
Module call
- Custom ErrorCodes for each module, without interfering with each other
- Define the ErrorCode type through generic parameters
- Use Result<Success, Failure> to eliminate the optional value of the result, choose one of the two for success and failure, and distinguish the processing
- Restrict the failed Error type, only the current module and basic errors need to be handled, no need to pay attention to other types of errors
Before optimization
public func queryDemo(with params: [String: String], completionHandler: @escaping (_ model: DemoModel?, _ code: ResponseCodeType) -> Void) { ((params)) { model in // ... let code = ?? -1 let type = ResponseCodeType(rawValue: code) ?? .other guard type == .success, let result = (from: ) else { completionHandler(nil, type) return } completionHandler(.success(resultModel)) } }
(with: params) { model, code in // Can only determine the success or failure of the interface by unpacking the model guard let model = model else { // Failure handling handleFail(code: code) return } // Successfully processed hanldeSuccess() } private func handleFail(code: ResponseCodeType) { // ... // Current module error handling let showWarning = code == .wrongTeamCode || code == .classNotFound // UI processing = !showWarning // hint () }
After optimization
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)) { // Base class request((params)) { result in switch result { case .success(let success): // Result processing queue if let resultModel = (from: ) { // Convert module model model completionHandler(.success(resultModel)) } else { // Conversion failed, default other completionHandler(.failure(.type(.other))) } case .failure(let error): // The module thrown error completionHandler(.failure(error)) } }
(with: params) { result in // Divide the result state through Result switch result { case .success(let model): // Successfully processed hanldeSuccess() case .failure(let error): // Failure handling handleError(error) } } // The example is simple processing. If you need to fine-tune the errors and split the optimized code, the logic will be obviously clearer.private func handleError(_ error: ModuleRespError<StudyGroupRespCode>) { switch error { case .type(let code): // ... // Current module error handling let showWarning = code == .wrongTeamCode || code == .classNotFound // UI processing = !showWarning // hint () case .baseType(let error): // Base class error handling () } }
Summarize
So far, we have learned about the general logic of ErrorCode's reconstruction and optimization. From the subsequent development process results, we can see that there is indeed good control over the chaotic growth of the project's Code. Each module only needs to pay attention to handling its own exception code, which reduces the difficulty of maintaining the code, and will continue to pay attention and optimize in the future.
References
- Result or Result
The above is the detailed explanation of Swift Error reconstruction optimization. For more information about Swift Error reconstruction optimization, please pay attention to my other related articles!