使用协议的 Swift 通用解码器
Posted
技术标签:
【中文标题】使用协议的 Swift 通用解码器【英文标题】:Generic Decoder for Swift using a protocol 【发布时间】: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 感谢您的提示。但也许你也可以给我一个建议,如何编写完成处理程序来处理通用模型。 【参考方案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】:
我可以向您建议如何通过使用Alamofire
将Decodable
与您的API 调用结构一起使用。
我创建了继承自SessionManager
的RequestManager
类,并在其中添加了对所有人通用的请求调用。
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<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
注意:-一些变量,模型类是我自定义的。你可以用你的替换它。
【讨论】:
以上是关于使用协议的 Swift 通用解码器的主要内容,如果未能解决你的问题,请参考以下文章