Consumiendo una API REST en Swift 6: Adiós a las librerías externas

Consumiendo una API REST en Swift 6: Adiós a las librerías externas

Durante años, la comunidad de desarrollo iOS ha dependido casi ciegamente de librerías de terceros como Alamofire para gestionar las peticiones de red. Era lógico: trabajar con URLSession en las primeras versiones de Swift resultaba verboso, complejo y propenso a errores. Sin embargo, con la llegada de Swift 6, el panorama ha cambiado drásticamente.

Hoy en día, el lenguaje ha madurado lo suficiente como para que el uso de dependencias externas para tareas básicas de red sea, en muchos casos, innecesario. En este artículo, exploraremos cómo consumir una API REST utilizando las herramientas nativas de Swift 6, aprovechando el modelo de concurrencia estructurada y las mejoras en seguridad de tipos.


1. El fin de una era: ¿Por qué dejar de usar librerías externas?

Antes de entrar en código, es vital entender por qué el enfoque «nativo primero» está ganando tanto terreno. No se trata de una moda, sino de una decisión técnica basada en varios pilares:

  • Reducción del tamaño del binario: Cada librería añade peso a tu aplicación. En proyectos pequeños o medianos, incluir Alamofire puede ser excesivo si solo vas a realizar un puñado de peticiones GET y POST.
  • Seguridad y Mantenimiento: Al no depender de terceros, no tienes que esperar a que los mantenedores actualicen la librería para que sea compatible con la nueva versión de Xcode o de Swift.
  • Curva de aprendizaje: Swift 6 ha simplificado tanto la sintaxis que aprender la API nativa es ahora igual de intuitivo que aprender una librería externa.
  • Concurrencia Nativa: Swift 6 introduce un modelo de aislamiento de datos y seguridad en hilos (Strict Concurrency) que funciona perfectamente con URLSession.

2. El corazón del intercambio de datos: Codable

Cualquier comunicación con una API REST moderna se basa en el formato JSON. En Swift, la herramienta definitiva para esto es el protocolo Codable. Gracias a él, podemos convertir una respuesta JSON directamente en objetos Swift con apenas un par de líneas de código.

Ejemplo de modelo de datos:

Supongamos que vamos a consumir una API de usuarios. Nuestro modelo en Swift 6 se vería así:

struct User: Codable, Sendable {
    let id: Int
    let name: String
    let email: String
    let username: String
}

Nota: Hemos añadido el protocolo Sendable. En Swift 6, esto es fundamental para garantizar que el objeto pueda ser transferido de forma segura entre diferentes contextos de concurrencia (como del hilo de red al hilo principal).


3. Implementando la capa de red con URLSession y Async/Await

La verdadera magia ocurre con URLSession. Con la introducción de async/await, el código que antes requería múltiples cierres (closures) y anidamientos ahora se lee de forma secuencial y limpia.

Creando una función de petición básica

Aquí tienes cómo realizar una petición GET de forma nativa:

func fetchUsers() async throws -> [User] {
    let urlString = "https://jsonplaceholder.typicode.com/users"

    guard let url = URL(string: urlString) else {
        throw URLError(.badURL)
    }

    // Realizamos la petición de forma asíncrona
    let (data, response) = try await URLSession.shared.data(from: url)

    // Verificamos que la respuesta sea un HTTP 200 OK
    guard let httpResponse = response as? HTTPURLResponse, 
          httpResponse.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }

    // Decodificamos el JSON
    let decoder = JSONDecoder()
    return try decoder.decode([User].self, from: data)
}

4. Gestión de errores profesional en Swift 6

Uno de los errores más comunes al dejar las librerías externas es no implementar una gestión de errores robusta. En lugar de lanzar errores genéricos, es mejor crear un enumerado que describa qué ha fallado exactamente.

Definición de errores personalizados

enum NetworkError: Error {
    case invalidURL
    case requestFailed
    case decodingError
    case unauthorized
    case serverError(Int)
}

Al usar un enfoque nativo, tienes el control total sobre cómo reaccionar ante un error 401 (no autorizado) o un 500 (error del servidor), permitiéndote implementar lógicas de reintento o cierre de sesión de manera personalizada.


5. El poder de los Genéricos: Un NetworkManager reutilizable

No queremos escribir el código de decodificación y validación de respuesta en cada función. Para trabajar como un profesional en Swift 6, debemos crear una capa genérica que maneje cualquier tipo de petición.

Implementación del NetworkManager

final class NetworkManager: Sendable {
    static let shared = NetworkManager()
    private init() {}

    func request(url: URL) async throws -> T {
        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.requestFailed
        }

        switch httpResponse.statusCode {
        case 200...299:
            do {
                return try JSONDecoder().decode(T.self, from: data)
            } catch {
                throw NetworkError.decodingError
            }
        case 401:
            throw NetworkError.unauthorized
        default:
            throw NetworkError.serverError(httpResponse.statusCode)
        }
    }
}

Este pequeño fragmento de código reemplaza gran parte de lo que solíamos buscar en librerías externas. Es escalable, seguro y extremadamente eficiente.


6. Integración con la Interfaz de Usuario (SwiftUI)

Swift 6 y SwiftUI trabajan en perfecta armonía. Gracias al modificador .task, podemos llamar a nuestras funciones de red de manera que se cancelen automáticamente si el usuario navega fuera de la vista antes de que termine la petición.

Ejemplo en una Vista:

struct UserListView: View {
    @State private var users: [User] = []
    @State private var errorMessage: String?

    var body: some View {
        NavigationView {
            List(users, id: \.id) { user in
                VStack(alignment: .leading) {
                    Text(user.name).font(.headline)
                    Text(user.email).font(.subheadline)
                }
            }
            .navigationTitle("Usuarios")
            .task {
                await loadData()
            }
        }
    }

    private func loadData() async {
        do {
            let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
            self.users = try await NetworkManager.shared.request(url: url)
        } catch {
            self.errorMessage = error.localizedDescription
        }
    }
}

7. Buenas prácticas al usar Networking nativo en Swift 6

Para que tu implementación sea realmente de nivel senior, considera seguir estas recomendaciones:

  1. Usa URLComponents para URLs complejas: No concatenes strings para añadir parámetros de búsqueda. Usa URLComponents para evitar errores de codificación de caracteres.
  2. Configura URLSessionConfiguration: Si necesitas timeouts específicos o políticas de caché personalizadas, no uses .shared. Crea tu propia instancia de URLSession.
  3. Seguridad con Sendable: Asegúrate de que todos tus modelos de datos cumplen con Sendable para evitar advertencias de concurrencia en Swift 6.
  4. Logging: Implementa un sistema de logs nativo usando el framework OSLog para depurar tus peticiones de red sin llenar la consola de print().

8. Conclusión

La evolución de Swift ha llegado a un punto de madurez donde la dependencia de librerías externas de networking ya no es la norma, sino la excepción. Swift 6, con su enfoque en la seguridad de la concurrencia y la simplicidad de async/await, nos proporciona todas las herramientas necesarias para construir capas de red robustas, rápidas y fáciles de mantener.

Al adoptar URLSession y Codable, no solo reduces el peso de tu app, sino que también profundizas en el funcionamiento interno del sistema operativo, lo que te convierte en un desarrollador iOS más completo. Es hora de decir adiós a las dependencias innecesarias y abrazar el poder de lo nativo.

¿Estás listo para migrar tu proyecto a un networking 100% nativo? El código que escribas hoy será mucho más fácil de mantener mañana.

Deja una respuesta