【问题标题】:Generic Decoder for Swift using a protocol使用协议的 Swift 通用解码器
【发布时间】:2020-01-13 15:55:33
【问题描述】:

我尝试使用协议为我的所有模型使用通用 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 给我错误:协议类型“可编码”(又名“可解码和可编码”)不能符合“可解码”,因为只有具体类型才能符合协议。但是我怎样才能实现一个通用的解码器并传递给他们不同的模型。我想我必须修改我的协议定义,但是如何修改?我想传递模型,然后接收模型的解码数据(在我的示例 modelData 中)。很明显,当我编写时程序运行: func getData(from endPoint: Endpoint, completion: @escaping(Result, APIError>) 我的意思是当我使用具体的Model时,但我想传递模型,以便我可以将类用于不同的模型。

谢谢, 阿诺德

【问题讨论】:

  • 阅读本系列文章:robnapier.net/start-with-a-protocol
  • 感谢您的提示。但也许你也可以给我一个建议,如何编写完成处理程序来处理通用模型。

标签: swift protocols


【解决方案1】:

协议不能符合自己,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)
}

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

【讨论】:

  • 我刚刚学到的一个让这变得更好的技巧:将getData() 的第一个参数设为_: T.Type = T.self。这样,您可以在函数签名中传递类型,而不必将其包含在闭包中(这通常有点难看)。但如果很容易推断,你就不必通过了。
  • @RobNapier 听起来确实不错。以及如何使用函数来推断类型?
  • 如果你传递一个文字闭包,那么你可以称之为getData(Something.self, from: endpoint) { ... }),你不需要把类型放在闭包中。如果你传递一个闭包变量(所以类型是已知的),你可以调用getData(from: endpoint, completion: completion)
【解决方案2】:

我可以向您建议如何通过使用AlamofireDecodable 与您的API 调用结构一起使用。

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

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)
        }
    }
} 

然后在另一个类创建 NetworkManager 类之后,该类包含所需的 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 是我的自定义类,用于在 api 调用时显示进度视图。

之后,我创建了DataManager 类,它将帮助我创建请求 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 类来调用基于模块的Web 服务。所以我将创建 Swift 文件并扩展 DataManager 类并调用相关 API。

见下文,在 API 调用中,我会将相关模型返回到 Result,如 Result&lt;StoreListModel, APIError&gt;

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
        }
    }

注意:-一些变量,模型类是我自定义的。你可以用你的替换它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多