Cliente HTTP en Swift: haciendo solicitudes correctamente

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué un cliente HTTP correcto es importante para tu aplicación Swift

Las aplicaciones modernas de iOS y macOS rara vez prescinden de la interacción con servidores remotos. Ya sea para cargar datos desde una REST API, enviar análisis o trabajar con servicios en la nube — un cliente HTTP en Swift es la base de la comunicación en red. Sin embargo, muchos desarrolladores, especialmente los principiantes, cometen errores típicos: ignoran el manejo de errores, no utilizan las capacidades modernas del lenguaje (async/await) o crean "código espagueti" con llamadas de red. En este artículo analizaremos cómo escribir un cliente HTTP robusto y eficiente en Swift, siguiendo las mejores prácticas.



1. Fundamentos de URLSession: el corazón de las solicitudes de red

En la biblioteca estándar de Swift, la comunicación en red está a cargo del framework Foundation y la clase URLSession. Esta es una herramienta potente que soporta configuración de sesiones, almacenamiento en caché, solicitudes en segundo plano y mucho más.



1.1. Creación de un cliente HTTP básico

Comencemos con una estructura simple que realizará solicitudes GET. Usamos un enum para describir los posibles errores y un protocolo para la flexibilidad:

import Foundation

enum HTTPClientError: Error { case invalidURL case noData case decodingError(Error) case networkError(Error) case statusCode(Int)}

protocol HTTPClientProtocol { func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T}

final class HTTPClient: HTTPClientProtocol { private let session: URLSession private let decoder: JSONDecoder init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) { self.session = session self.decoder = decoder } func get<T: Decodable>(from urlString: String, responseType: T.Type) async throws -> T { guard let url = URL(string: urlString) else { throw HTTPClientError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw HTTPClientError.networkError(NSError(domain: "Invalid response", code: -1)) } guard (200...299).contains(httpResponse.statusCode) else { throw HTTPClientError.statusCode(httpResponse.statusCode) } do { return try decoder.decode(T.self, from: data) } catch { throw HTTPClientError.decodingError(error) } }}


1.2. ¿Por qué async/await?

Antes de la llegada de Swift 5.5, los desarrolladores usaban closures (completion handlers) o bibliotecas de terceros (Alamofire). Async/await hace que el código sea lineal, comprensible y fácil de leer. Los errores se manejan mediante do-catch, en lugar de closures anidados.



2. POST, PUT, DELETE: trabajo con diferentes métodos HTTP

Un cliente HTTP real debe soportar todos los métodos principales. Añadamos al protocolo métodos para enviar datos y actualizar recursos.



2.1. Extensión del protocolo para POST y PUT

extension HTTPClientProtocol {    func post<T: Decodable, U: Encodable>(        to urlString: String,        body: U,        responseType: T.Type    ) async throws -> T {        guard let url = URL(string: urlString) else {            throw HTTPClientError.invalidURL        }                var request = URLRequest(url: url)        request.httpMethod = "POST"        request.setValue("application/json", forHTTPHeaderField: "Content-Type")        request.setValue("application/json", forHTTPHeaderField: "Accept")        request.httpBody = try JSONEncoder().encode(body)                return try a                

Blogs

Book Recommendations