基于协议的通用网络-第2部分JSONEncoder和Swift中的Post请求可编码。

自从我在那篇文章中写了“在Swift 4中使用JSONDecoder和Decodable的基于协议的通用网络”以来,我一直在谈论如何使用Decodable,协议和泛型来创建可重用的网络层。

效果很好,但API仅限于GET HTTP方法,这就是为什么在本教程中我想将其扩展为使用POST方法,我将尽力向您展示如何使用枚举来完成这项工作,我假设您没有读过第1部分,因此在新项目中我将从零开始,您可以随时返回第一部分以了解更多详细信息。

我们将从GenericAPI类开始,创建一个新文件并复制并粘贴…

 协议GenericAPIClient {var session:URLSession {get} func fetch (带有请求:URLRequest,解码:@escaping(Decodable)-> T ?,完成:@escaping(Result )-> Void )}扩展名GenericAPIClient {typealias JSONTaskCompletionHandler =(Decodable ?, APIError?)->无效的私有函数解码任务(带有请求:URLRequest,decodeType:T.Type,completionHandler的完成:@转义JSONTaskCompletionHandler)-> URLSessionDataTask {let任务= session.dataTask(with:request){数据,响应,防护错误让httpResponse =响应为?  HTTPURLResponse否则{completion(nil,.requestFailed(description:error?.localizedDescription ??“无描述”))return} guard httpResponse.statusCode == 200 else {完成情况(nil,.responseUnsuccessful(description:“ \(http(Response.statusCode )“))return}保护let data = data else {complete(nil,.invalidData);  return} do {letgenericModel = try JSONDecoder()。decode(decodingType,from:data)complete(genericModel,nil)}捕获let err { }} return task} ///成功响应在主线程上执行。  func fetch (带有请求:URLRequest,解码:@escaping(Decodable)-> T ?,完成:@escaping(Result )->无效){let task = encodingTask(with:request ,encodingType:T.self){(DispatchQueue.main.async中的(json,错误){卫队让json = json其他{错误!=零?  complete(.failure(.decodingTaskFailure(description:“ \(String(describing:error))”))))):completion(.failure(.invalidData))return} guard let value =解码(json)else {complete(.failure (.jsonDecodingFailure)); 返回}完成(.success(值))}} task.resume()}} 

该协议扩展包含创建任务的所有代码,该任务根据指定的URL请求对象检索URL的内容,并且不对响应进行序列化,而是使用Decodable协议对其进行解码(同样,我建议继续第1部分,以获取更多信息)细节)。

现在您应该有一些编译错误,这是因为您将需要创建一些类,让我们从APIError开始,创建一个名为APIError复制并粘贴的新文件…

 枚举APIError:错误{case invalidData case jsonDecodingFailure case responseUnsuccessful(description:String)casecodingTaskFailure(description:String)case requestFailed(description:String)case jsonConversionFailure(description:String)case postParametersEncodingFalure(description:String)var customDescription:字符串{开关自我{case .requestFailed(let description):返回“ APIError-请求失败-> \(description)” case .invalidData:返回“ Invalid Data” case .responseUnsuccessful(let description):返回“ APIError-响应失败状态码-> \(描述)” case .jsonDecodingFailure:返回“ APIError-JSON解码失败” case .jsonConversionFailure(描述):返回“ APIError-JSON转换失败-> \(描述)” case .decodingTaskFailure(描述):返回“ APIError -解码任务失败,并显示错误-> \(描述)“ case .postParametersEncodingFalure(描述):返回“ APIError-发布参数失败-> \(描述)  “}}} 

在这里,我们开始使用具有关联类型的枚举来构造特定类型的错误,现在让我们构造另一个枚举,这次它将包含两种情况,一种用于成功的请求,一种用于失败的情况,创建一个新文件并将其命名为Result …

 枚举Result ,其中U:错误{案例成功(T)案例失败(U)} 

如果您不熟悉此语法,建议您在Swift上阅读有关泛型的信息,在线上有许多入门入门教程,但是如果您已经熟悉泛型并希望看到更高级的用法,请查看这篇文章。

好的,现在让我们为HTTP方法创建一个模型,创建一个新文件并将其命名为HTTPMethod,除了“ GET”和“ POST”以外,还有其他HTTP方法,但是本教程着重于向您展示如何使用Encodable来使用“ POST” ”因此将使该模型保持简单……

 枚举HTTPMethod:字符串{case post =“ POST” case get =“ GET”} 

如您所知,您将需要在POST方法中将某种类型的标头传递给您的请求,因此让我们创建一个模型来处理该标头,同样是新文件…

 枚举HTTPHeader {case contentType(String)case accept(String)case授权(String)var header:(field:String,value:String){切换self {case .contentType(let value):return(field:“ Content-Type “,value:value)case .accept(let value):return(field:” Accept“,value:value)case .authorization(let value):return(field:” Authorization“,value:value)}}}} 

有关HTTP标头的完整列表,请检查此链接,您可以使用完整列表扩展此枚举!

好的,现在我们需要一个端点,我真的很喜欢使用我在Treehouse上看到的实现的方法,使用不同端点处理字符串很容易变得一团糟,因此,通过使用此协议扩展,我们稍后使用枚举来管理所有这些端点在一个地方,创建一个新文件并将其命名为Endpoint…。

 协议端点{var base:字符串{get} var路径:String {get}}扩展端点{var urlComponents:URLComponents?  {保护var组件= URLComponents(string:base)否则{return nil} components.path =路径返回组件} var请求:URLRequest?  {保护let url = urlComponents?.url ??  URL(string:“ \(self.base)\(self.path)”)else {return nil} let request = URLRequest(url:url)返回请求}} 

在这里,我们只是构造一个具有基本路径和用于定向请求的路径的终结点,如果您已经看过第1部分,那么对不起,到目前为止,大部分内容都来自该教程,但这是个好部分,让我们添加一个功能将构造一个POST请求,在此协议扩展内添加此功能…

  func postRequest (参数:T,标头:[HTTPHeader])-> URLRequest?  {Guard var request = self.request else {return nil} request.httpMethod = HTTPMethod.post.rawValue do {request.httpBody = try JSONEncoder()。encode(parameters)} catch let error {print(APIError.postParametersEncodingFalure(description: “ \(错误)”)。customDescription)返回nil} headers.forEach {request.addValue($ 0.header.value,forHTTPHeaderField:$ 0.header.field)}返回请求} 

噢,我爱泛型,协议和枚举,它们确实有助于减少代码重复,让我向您展示为什么,函数签名中的parameter参数是符合Encodable的泛型类型,因此只要它可以接受任何类型的模型它符合此协议,因此我们无需为每个不同的模型创建新的不同函数,headers参数是HTTPHeader对象数组,这是我们不久前创建的枚举。

在函数内部,我们检查是否存在请求,然后使用HTTPMethod枚举大小写在请求中定义httpMethod,随后使用JSONEncoder对模型进行编码,最后对每个模型执行一次,以将HTTPField及其对应的值添加到请求标头,全部完成! 这就是我们所需要的,那么您将如何使用它? 很好,我很抱歉,但是为简单起见,我不会使用任何API来执行真实的请求,但是我将向您展示如何逐步使用它,比如说我们正在使用API​​来检索Netflix上您喜欢的节目,我是Narcos(无论如何,对于即将到来的第4季非常兴奋),因此这是符合Endpoint的对象的外观……。

 枚举NarcosFeed { 
案例纳奇
案件警察
}扩展名NarcosFeed:端点{

var base:字符串{
返回“ https://api.narcos.org”
}

var path:字符串{
切换自我{
case .narcs:返回“ / 3 / narcs”
case .police:返回“ / 3 / police”
}
}
}

您在这里得到图片吗? 假设现在要向API添加“政治”,只需添加一个新案例及其对应的路径,好了,假设我们的APP让用户搜索节目的角色,首先您需要一个看起来像像这样解码响应…

 结构Narc:可解码{让名称:字符串} 

然后,假设获取字符的API使用POST请求,该请求将ID可以作为名称(例如“ Pablo”)作为参数,它还需要标头具有“ application / json”值的“ Content-Type”键,因此您将需要一个对象来编码所需的参数,它看起来像这样……

  struct NarcParameter:可编码{let id:String} 

现在,您将需要创建一个符合GenericAPIClient协议的客户端,并在其中创建应用程序所需数据所需的所有功能,看起来就像这样……

 类NarcosClient:GenericAPIClient {内部让会话:URLSession初始化(配置:URLSessionConfiguration){self.session = URLSession(配置:配置)}便利init(){self.init(配置:.default)} ///获取Narcs字符名称 func fetchNarcs(id:字符串,完成:@转义(Result )->()){let parameter = NarcParameter(id:id)Guard let request = NarcosFeed.narcs.postRequest(parameters:parameter,标头:[HTTPHeader.contentType(“ application / json”)])else {return} fetch(with:request,解码:{json-> Narc?in guard let result = json as?Narc else {return nil} return results} ,完成:完成)}} 

在fetchNarcs函数内部,您可以看到我们如何使用之前编写的所有组件,使用相应的路径“ / narcs”构造请求,然后Endpoint协议中的函数采用参数和标头。

最后,这是对您的NarcosClient的典型调用的样子……

  NarcosClient()。fetchNarcs(id:“ Pablo”){导致切换结果{case .success(let narc):print(“ plato o plomo ... \(String(describing:narc))”)case .failure(让错误):print(“错误\(错误)”)}} 

现在,您将看到Result枚举如何轻松地帮助我们确定请求的状态并采取相应的行动。

希望对您有所帮助,再次,如果您已经阅读了第1部分,我对重复的内容表示歉意,但是它确实帮助我解释了如何按照第1部分中的相同方法创建POST请求,可以找到此示例的完整示例。关于这个仓库的教程。

谢谢!