SoFunction
Updated on 2025-04-11

Detailed explanation of Swift Error Refactoring Optimization

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&lt;T: ISTargetType&gt;(_ server: T,
                                                 result: @escaping ((Result&lt;NetWorkResponseModel, ModuleRespError&lt;&gt;&gt;) -&gt; 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&lt;&gt; = .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&lt;ClassItemModel, ModuleRespError&lt;StudyGroupRespCode&gt;&gt;) -&gt; 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&lt;StudyGroupRespCode&gt;) {
	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!