Swift 使用协议的通用解码器

Generic Decoder for Swift using a protocol

我尝试为我所有使用协议的模型使用通用 Json 解码器。

//这里定义协议:

func fetch<T: Decodable>(with request: URLRequest, decode: @escaping (Decodable) -> T?, completion: @escaping (Result<T, APIError>) -> Void) {.. other Code}

//这里实现:

func getData(from endPoint: Endpoint, completion: @escaping (Result<ApiResponseArray<Codable>, APIError>) -> Void) {

        let request = endPoint.request

        fetch(with: request, decode: { json -> Decodable in
           guard let dataResult = json as? modelData else { return  nil }
           return dataResult
        }, completion: completion)
    }

ApiResponseArray 给我错误:协议类型 'Codable'(又名 'Decodable & Encodable')不能符合 'Decodable' 因为只有具体类型才能符合协议。但是我如何实现通用解码器并向它们传递不同的模型。我想我必须修改我的协议定义,但是如何修改呢?我想传递模型,然后接收模型的解码数据(在我的示例 modelData 中)。很明显程序在我写的时候运行: func getData(from endPoint: Endpoint, completion: @escaping (Result, APIError>) 我的意思是当我使用具体模型时,但我想传递模型,以便我可以将 class 用于不同的模型。

谢谢, 阿诺德

我可以通过 Alamofire.

向您建议如何将 Decodable 与 API 调用结构一起使用

我创建了 RequestManager class 继承自 SessionManager 并在其中添加了对所有人通用的请求调用。

class RequestManager: SessionManager {

    // Create shared instance
    static let shared = RequestManager()

    // Create http headers
    lazy var httpHeaders : HTTPHeaders = {
        var httpHeader = HTTPHeaders()
        httpHeader["Content-Type"] = "application/json"
        httpHeader["Accept"] = "application/json"
        return httpHeader
    }()


    //------------------------------------------------------------------------------
    // MARK:-
    // MARK:- Request Methods
    //------------------------------------------------------------------------------

    func responseRequest(_ url: String, method: Alamofire.HTTPMethod, parameter: Parameters? = nil, encoding: ParameterEncoding, header: HTTPHeaders? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Void {

        self.request(url, method: method, parameters: parameter, encoding: encoding, headers: header).response { response in
            completionHandler(response)
        }
    }
} 

然后再 class 创建了 NetworkManager class 之后 NetworkManager class 需要 get/post 方法调用并通过 JSONDecoder 解码 json 如下:

class NetworkManager {

    static let shared                   = NetworkManager()
    var progressVC                      : ProgressVC?

    //----------------------------------------------------------------
    // MARK:-
    // MARK:- Get Request Method
    //----------------------------------------------------------------

    func getResponse<T: Decodable>(_ url: String, parameter: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, header: HTTPHeaders? = nil, showHUD: HUDFlag = .show, message: String? = "Please wait...", decodingType: T.Type, completion: @escaping (Decodable?, APIError?) -> Void) {

        DispatchQueue.main.async {
            self.showHideHud(showHUD: showHUD, message: "")
        }

        RequestManager.shared.responseRequest(url, method: .get, parameter: parameter, encoding: encoding, header: header) { response in

            DispatchQueue.main.async {
                self.showHideHud(showHUD: .hide, message: "")
            }

            guard let httpResponse = response.response else {
                completion(nil, .requestFailed("Request Failed"))
                return
            }

            if httpResponse.statusCode == 200 {
                if let data = response.data {
                    do {
                        let genericModel = try JSONDecoder().decode(decodingType, from: data)
                        completion(genericModel, nil)
                    } catch {

                        do {

                            let error = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]

                            if let message = error!["message"] as? String {

                                completion(nil, .errorMessage(message)!)

                            } else if let message = error!["message"] as? Int {

                                completion(nil, .errorMessage(String(describing: "Bad Request = \(message)")))
                            }

                        } catch {
                            completion(nil, .jsonConversionFailure("JSON Conversion Failure"))
                        }
                    }
                } else {
                    completion(nil, .invalidData("Invalid Data"))
                }
            } else {
                completion(nil, .responseUnsuccessful("Response Unsuccessful"))
            }
        }
    }
}

ProgressVC 是我的习惯 class 在 api 调用时显示进度视图。

之后,我创建了 DataManager class,这将帮助我创建请求 url。

class DataManager: NSObject {

    //------------------------------------------------------------------------------
    // MARK:- Variables
    //------------------------------------------------------------------------------

    static let shared   = DataManager()
    let baseUrl         = WebServiceURL.local    


    //------------------------------------------------------------------------------
    // MARK:- Custom Methods
    //------------------------------------------------------------------------------

    // Get API url with endpoints
    func getURL(_ endpoint: WSEndPoints) -> String {
        return baseUrl + endpoint.rawValue
    }
}

我创建了以下枚举以在我的完成块中发送数据或错误。

enum Result<T, U> where U: Error  {
    case success(T)
    case failure(U)
}

这是错误列表,其中存储了与 api 调用期间触发的状态相关的自定义消息。

enum APIError: Error {
    case errorMessage(String)
    case requestFailed(String)
    case jsonConversionFailure(String)
    case invalidData(String)
    case responseUnsuccessful(String)
    case jsonParsingFailure(String)

    var localizedDescription: String {

        switch self {

        case.errorMessage(let msg):
            return msg

        case .requestFailed(let msg):
            return msg

        case .jsonConversionFailure(let msg):
            return msg

        case .invalidData(let msg):
            return msg

        case .responseUnsuccessful(let msg):
            return msg

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

然后,我将扩展这个DataManager class 来调用基于模块的web 服务。所以我将创建 Swift 文件并扩展 DataManager class 并调用相对 API.

见下文,在 API 调用中我将 return 相关模型转换为 ResultResult<StoreListModel, APIError>

extension DataManager {

    // MARK:- Store List
    func getStoreList(completion: @escaping (Result<StoreListModel, APIError>) -> Void) {

        NetworkManager.shared.getResponse(getURL(.storeList), parameter: nil, encoding: JSONEncoding.default, header: getHeaders("bd_suvlascentralpos"), showHUD: .show, message: "Please wait...", decodingType: StoreListModel.self) { (decodableData, apiError) in

            if apiError != nil {

                completion(.failure(apiError!))

            } else {

                guard let userData = decodableData as? StoreListModel else {
                    completion(.failure(apiError!))
                    return
                }

                completion(.success(userData))
            }
        }
    }
}

从请求的完成块中,我将获得可解码的数据,在这里可以安全地键入 cast。

使用:

DataManager.shared.getStoreList { (result) in

        switch result {

        case .success(let storeListModel):

            if let storeList = storeListModel, storeList.count > 0 {
                self.arrStoreList = storeList

                self.tblStoreList.isHidden = false
                self.labelEmptyData.isHidden = true

                self.tblStoreList.reloadData()
            } else {
                self.tblStoreList.isHidden = true
                self.labelEmptyData.isHidden = false
            }
            break

        case .failure(let error):
            print(error.localizedDescription)
            break
        }
    }

注意:-一些变量,模型classes是我的习惯。您可以将其替换为您的。

协议不能符合自身,Codable必须是具体类型或只能用作泛型约束。

在你的上下文中你必须做后者,像这样

func fetch<T: Decodable>(with request: URLRequest, decode: @escaping (Data) throws -> T, completion: @escaping (Result<T, APIError>) -> Void) {  }

func getData<T: Decodable>(_ : T.Type = T.self, from endPoint: Endpoint, completion: @escaping (Result<T, APIError>) -> Void) {

    let request = endPoint.request

    fetch(with: request, decode: { data -> T  in
        return try JSONDecoder().decode(T.self, from: data)
    }, completion: completion)
}

网络请求通常returns Data作为decode闭包的参数类型更合理