Bienvenido, soy Miguel y hoy les traigo un post.
Índice
Prepare una solicitud de API, llame a una API y analice la respuesta
Este artículo se centra en la simplicidad, personalización , y la modularidad de la capa de red. Mi enfoque ha sido probado en muchos proyectos y he estado actualizando mi capa de red para incorporar los problemas que enfrenté durante el desarrollo.
Reglas de oro
Antes de comenzar a leer este artículo:
- Los controladores de vista nunca deberían conocer las API de red. Ni siquiera debería importarles si los datos provienen de la API o de la base de datos local, por ejemplo, Coredata o SQLite.
- La única capa que debería llamar a la API de red es la capa de servicio.
- Los controladores de vista deben solicitar al servicio que proporcione los datos.
- El servicio no debe conocer el servidor API y el formato de los datos que se envían o reciben hacia o desde el servidor API.
- Solo la capa de red debe conocer estos detalles.
Lo que cubrirá esta pieza:
- Prepare una solicitud de API: <ApiRequest>
- Llame a una API: <Alamofire>
- Respuesta de análisis: < Codificable >
1. Prepare una solicitud
Cada solicitud de red está identificada por ciertas propiedades:
- Punto final: La ruta a la API de red
- Tipo de método HTTP: GET, POST, PUT, DELETE, PATCH, etc.
- Solicitar parámetros: JSON o una cadena
- Tipo de respuesta: JSON, XML o una cadena
Punto final
Tomemos un ejemplo de una API que devuelve las historias por un nombre de usuario. get-stories-by-username
// GET All stories by usernamehttps:manualestutor.com/api/v1.0/stories/waseem_khan// server-url/api-path/api-version/resource-name/end-point// server-url -> https:manualestutor.com/ // api-path -> api/ // api-version -> v1.0/ // resource-name -> stories/ // end-point -> waseem_khan
La mayoría de las veces, la URL de cualquier API tiene el formato que mencioné. Por lo general, el server-url
sigue siendo el mismo, pero el resto de las propiedades están sujetas a cambios por API. Nuestra capa de red debe estar escrita de manera que admita la personalización de cada solicitud de API.
Encapsulemos nuestra solicitud de red en una clase Swift.
// // ApiRequest.swift // Created by waseem on 10/1/19 // import Foundation import SwiftyJSON class ApiRequest<ResponseType: Codable> { func webserviceUrl() -> String { #if DEBUG return "https:www.dev.mamualestutor.com/" #else return "https:www.manualestutor.com/" #endif } func apiPath() -> String { return "api/" } func apiVersion() -> String { return "v1.0/" } func apiResource() -> String { return "" } func endPoint() -> String { return "" } func bodyParams() -> NSDictionary? { return [:] } func requestType() -> HTTPMethod { return .post } func contentType() -> String { return "application/json" } }
ApiRequest
ApiRequest
class es el padre de todas las solicitudes de API que tendremos en nuestro proyecto, es decir, crearemos una clase Swift separada para cada API, por ejemplo, LoginApiRequest
, SignupApiRequest
, GetStoriesApiRequest
. Cada solicitud se puede personalizar completamente anulando los métodos necesarios de la clase principal.
Tipo de método HTTP
Cada API puede ser de uno de estos tipos: GET, POST, PUT, DELETE, PATCH. HTTPMethod
enum representa el tipo de solicitud de API.
// // HttpMethod.swift // Created by waseem on 10/1/19 // import Foundation public enum HTTPMethod: String { case options = "OPTIONS" case get = "GET" case head = "HEAD" case post = "POST" case put = "PUT" case patch = "PATCH" case delete = "DELETE" case trace = "TRACE" case connect = "CONNECT" }
Solicitar parámetros
Según el tipo de API, es posible que deba configurar los parámetros de solicitud en el cuerpo. Por ejemplo,LoginRequest
puede esperar parámetros de solicitud en el formato siguiente.
"email": "waseem.khan@manualestutor.com", "password": "mission#impossible"
Estos parámetros pueden proporcionarse anulando la función bodyParams()
en el LoginRequest
desde la base APIRequest
clase.
Tipo de respuesta
En el ApiRequest
clase <ResponseType: Codable>
, genérico ResponseType
de tipo Codable
es proporcionar el Codable
objeto modelo para mapear la respuesta devuelta desde la API. Podemos pasar cualquier objeto que esté implementando Codable
interfaz aquí. Array
, String
, Double
y otros tipos de datos, ya se ajustan a este protocolo. Podemos mapear directamente la respuesta de la API a uno de nuestra clase de modelo. Por ejemplo, get-stories-by-username
API devuelve los datos en el formato siguiente. Nos preocupa la respuesta en clave. <ResponseType: Codable>
ResponseType
Codable
Codable
Codable
Array
String
Double
get-stories-by-username
data
// REQUEST // GET All stories by username https:www.manualestutor.com/api/v1.0/stories/waseem_khan // Response { "isSuccess": true, "message": "Data loaded successfully", "data": [ { "storyId": 101, "clapsCount": 10, "title": "Highly Customizable Network layer in Swift 5", "published": true }, { "storyId": 102, "clapsCount": 0, "title": "Are you an architect for an iOS app?", "published": false } ] }
La respuesta a la API anterior se puede asignar directamente a la matriz de historias, es decir, [Story]
.
// // Story.swift // Created by waseem on 10/1/19 // import UIKit class Story: Codable { var storyId: Int? var clapsCount: Int? var title: String? var published: Bool? }
¿Por qué nuestra clase modelo Story
implementar Codable
?
// A type that can convert itself into and out of an external representation.typealias Codable = Decodable & Encodable
Mapearemos la respuesta JSON para la API get-all-stories a Story
. Por lo tanto Story
debería implementar Codable
para apoyar la codificación y decodificación.
Creemos nuestra primera solicitud de API de red
Creemos una API que obtenga historias de un usuario en particular de manualestutor.com. Esta API espera un límite de registros y un número de página (suponiendo que la paginación esté habilitada) en el cuerpo de la solicitud.
// // GetStoriesByUsernameRequest.swift // Created by waseem on 10/1/19 // import Foundation class GetStoriesByUsernameRequest: ApiRequest<[Story]> { var username: String! var limit: Int? var pageNumber: Int? override func apiResource() -> String { return "stories/" } override func endPoint() -> String { return username } override func bodyParams() -> NSDictionary? { return ["limit": limit!, "pageNumber": pageNumber!] } override func requestType() -> HTTPMethod { return .get } }
Verá que solo hemos anulado propiedades configurables para esta API. Podemos anularapiPath()
, apiVersion()
, etc., si es necesario. También hemos mencionado que esta API devolverá una serie de historias. [Story]
.
2. Llamar a una API
Ya hemos creado una solicitud de red. Ahora veamos cómo llamar realmente a la API de red. Usaremos el Alamofire
para comunicarnos con el servidor API. Creemos NetworkApiClient
que se encargará de la conectividad al servidor API.
// // NetworkApiClient.swift // Created by waseem on 10/1/19 // import UIKit import Alamofire import SwiftyJSON typealias ResponseHandler = (ApiResponse) -> Void class NetworkApiClient { func callApi<ResponseType>(request: ApiRequest<ResponseType>, responseHandler: @escaping ResponseHandler) { let urlRequest = urlRequestWith(apiRequest: request) Alamofire.request(urlRequest).responseData { (response) in switch(response.result) { case .success: let apiResponse = self.successResponse(request: request, response: response) responseHandler(apiResponse) case .failure: self.failureResponse(response: response) } } } func urlRequestWith<ResponseType>(apiRequest: ApiRequest<ResponseType>) -> URLRequest { let completeUrl = apiRequest.webserviceUrl() + apiRequest.apiPath() + apiRequest.apiVersion() + apiRequest.apiResource() + apiRequest.endPoint() var urlRequest = URLRequest(url: URL(string: completeUrl)!) urlRequest.httpMethod = apiRequest.requestType().rawValue urlRequest.setValue(apiRequest.contentType(), forHTTPHeaderField: "Content-Type") urlRequest.httpBody = try?JSONSerialization.data(withJSONObject: apiRequest.bodyParams()!, options: []) return urlRequest } ... }
// // ApiResponse.swift // Created by waseem on 10/1/19 // import UIKit class ApiResponse { var success: Bool! // whether the API call passed or failed var message: String? // message returned from the API var data: AnyObject? // actual data returned from the API init(success: Bool, message: String? = nil, data: AnyObject? = nil) { self.success = success self.message = message self.data = data } }
3. Analizar la respuesta
¿Qué tenemos hasta ahora?
- Una solicitud de red, es decir,
get-stories-by-username
- A para enviar la solicitud al servidor API
NetworkAPIClient
Ahora veamos cómo analizar la respuesta devuelta por la API.
Vuelva a NetworkApiClient
y proporcione la implementación de la función successResponse()
.
// // NetworkApiClient.swift // Created by waseem on 10/1/19 // import UIKit import Alamofire import SwiftyJSON typealias ResponseHandler = (ApiResponse) -> Void class NetworkApiClient { ... // here we are going to parse the data func successResponse<ResponseType: Codable>(request: ApiRequest<ResponseType>, response: DataResponse<Data>) -> ApiResponse{ do { // Step 1 let responseJson = try JSON(data: response.data!) // Step 2 let dataJson = responseJson["data"].object let data = try JSONSerialization.data(withJSONObject: dataJson, options: []) // Step 3 let decodedValue = try JSONDecoder().decode(ResponseType.self, from: data) return ApiResponse(success: true, data: decodedValue as AnyObject) } catch { return ApiResponse(success: false) } } ... }
Paso 1: Analizar la respuesta a un objeto JSON (usando SwiftyJson
)
Paso 2: Los datos / resultados reales están dentro de la data
clave, como se discutió en la respuesta general de la API anterior.
Paso 3: Aquí está el trato real.
Ya hemos proporcionado el tipo de clase de modelo que se espera de la API. En caso de solicitud get-stories-by-username
, el tipo mencionado es [Story]
, es decir, una lista de Historias. decodedValue
es de [Story]
tipo.
Pon todo a prueba
El conocimiento sin práctica es inútil; la práctica sin conocimiento es peligrosa.
¿Qué tenemos hasta ahora?
- Una solicitud de red, es decir,
get-all-stories-by-username
- Un cliente de API de red para:
> enviar la solicitud al servidor de API
> analizar la respuesta devuelta por el servicio de API
Ahora veremos cómo usar el conocimiento anterior para llamar a la API y usar el resultado.
Necesitamos crear una StoryService
clase que llame a la get-stories-by-username
API. StoryService
es responsable de todas las operaciones CRUD relacionadas con Story
.
// // StoryService.swift // Created by waseem on 10/1/19 // import UIKit typealias CompletionHandler = (Bool, AnyObject?) -> Void class StoryService { func fetchStoriesByUsername(username: String, limit: Int?, pageNumber: Int, completion: @escaping CompletionHandler) { let request = GetStoriesByUsernameRequest() request.username = username request.limit = limit request.pageNumber = pageNumber NetworkApiClient().callApi(request: request) { (apiResponse) in if apiResponse.success { completion(true, apiResponse.data) } else { completion(false, apiResponse.message as AnyObject?) } } } }
apiResponse.data
, En este caso, es una matriz de historias, es decir, [Story]
. Podemos usar esta lista para completar los datos en nuestro UITableView
controlador de vista.
Espero que todo quede claro. Puedes descargar el proyecto completo desde Github.
Añadir comentario