网络封装中 Error 处理

Posted by Genie on April 3, 2021

enum 是封装中一个常用的方式,enum 可以与其他实例进行绑定的,我们可以让方法返回枚举类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public struct ErrorMetaData: Codable {
    public var code: Int?
    public var msg: String?
}

public enum NetworkResponseError: Error {
    case error(errorData: ErrorMetaData)
    case parsingError(error: Error?)
    case requestFailed(error: Error?)
    case badRequest(error: Error?)
    case forbidden(error: Error?)
    case serverError(error: Error?)
    case redirected(error: Error?)
    case migration(error: Error?)
}

比如请求成功,我们会首先check https 握手🤝是否成功,

通过switch 把error返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private func checkStatusCode(with urlResponse: HTTPURLResponse, error: Error?) -> NetworkResponseError? {
        switch urlResponse.statusCode {
        case 200 ... 299: return nil
        case 300 ... 399:
            return NetworkResponseError.redirected(error: error)
        case 403:
            return NetworkResponseError.forbidden(error: error)
        case 400 ... 499:
            return NetworkResponseError.badRequest(error: error)
        case 500 ... 509:
            return NetworkResponseError.serverError(error: error)
        default: return nil
        }
    }

我们可以正常通过 error?.localizedDescription 获取到错误信息,比如超时请重试等 但是往往我们后台没有和http绑定,我们需要在成功链接后,还需要check response里面的 code message

// 200 but error in response

这个时候我们可以通过 extension NetworkResponseError 重写 LocalizedError errorDescriptioncode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
extension NetworkResponseError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case let .parsingError(error),
             let .requestFailed(error),
             let .badRequest(error),
             let .forbidden(error),
             let .serverError(error),
             let .redirected(error),
             let .migration(error):
            return error?.localizedDescription

        case let .error(error):
            return error.msg
        }
    }

    public var code: Int? {
        switch self {
        case let .error(error):
            return error.code
        default:
            return nil
            break
        }
    }
}

在回调中我们可以 print log

print(error?.localizedDescription ?? "no messages")

` if let error = error as? NetworkResponseError { print(error.code) } `

但在项目中会有些通用的error 处理,比如 强制退出,我们可以通过属性闭包来对其全局操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
     private func handleSuccessResponse(with data: Data, and urlResponse: URLResponse?,
                                       with completion: @escaping (R?, URLResponse?, NetworkResponseError?) -> Void) {
        NetworkLayer.didFinishWithSuccess?()
        if let urlRes = urlResponse?.url {
            print("\n[NW]")
            print("\nReceived: " + urlRes.description)
        }

        do {
            if var responseObject = try? JSONDecoder().decode(ErrorMetaData.self, from: data) {
                // 200 but error in response
                if let code = responseObject.code {
                    if code == 0 || code == 200 {
                        if var result = try? JSONDecoder().decode(ResponseObject.self, from: data) {
                            print("[\nNW]")
                            print("[S] Reponse: \n" + ((convertToDict(result)?.description) ?? ""))
                            completion(result, urlResponse, nil)
                        }
                    } else {
                        print("\n[NW]\n")
                        print("[S] Error Response: code = " + "\(code)" + "  msg = " + (responseObject.msg ?? "nil"))
                        if (responseObject.msg?.isEmpty ?? false) || responseObject.msg == nil {
                            responseObject.msg = "service error, try again later"
                        }
                        NetworkLayer.didExecuteWithError?(NetworkResponseError.error(errorData: responseObject))
                        completion(nil, nil, NetworkResponseError.error(errorData: responseObject))
                    }
                }
            }
        } catch let error as Error {
            print("[NW]")
            print("JSONSerialization" + "\(error)")
            completion(nil, nil, NetworkResponseError.parsingError(error: error))
        }
    }

我们在 NetworkLayer 定义几种闭包属性

1
2
3
4
5
6
 public struct NetworkLayer {
    public static var forceLogoutAction: (() -> Void)?
    public static var didFinishWithSuccess: (() -> Void)?
    public static var didFinishWithError: ((_ error: Error?) -> Void)?
    public static var didExecuteWithError: ((_ error: Error?) -> Void)?
}

在appdelegate 中call setUpNetWorkLayer(), 通过闭包来处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 extension AppDelegate {
    @objc func setUpNetWorkLayer() {
        NetworkLayer.forceLogoutAction = {
            ApplicationFlow.sharedInstance().doLogout()
        }

        NetworkLayer.didFinishWithSuccess = {

        }

        NetworkLayer.didFinishWithError = { error in
            print(error?.localizedDescription ?? "no messages")
        }

        NetworkLayer.didExecuteWithError = { error in
            if let error = error as? NetworkResponseError {
                print("error.localizedDescription" + "\(error.localizedDescription)")
                if let code = error.code {
                    if code == 401 || code == 501 { // logout
                        NetworkLayer.forceLogoutAction?()
                        return
                    }
                }
            }
        }
    }
}