wandres.dev
CONCURRENCIA · async/await y actores

Redes: URLSession y Codable

Consume APIs REST de forma moderna: peticiones async con URLSession y decodificación automática de JSON con Codable. De la nube a tu app, sin dolor.

⏱ 15 min

Casi ninguna app vive aislada: consultan el tiempo, cargan un feed, sincronizan con un servidor. Con async/await y Codable, hacer una petición de red y convertir la respuesta JSON en objetos Swift es sorprendentemente limpio.

🎯 Al terminar esta lección sabrás
  • Hacer una petición GET con URLSession y await.
  • Decodificar JSON automáticamente con Codable.
  • Manejar errores de red.
  • Un ejemplo completo de principio a fin.

Codable: JSON ⇄ Swift

Un tipo que conforma a Codable se convierte a/desde JSON solo. Si los nombres coinciden con las claves del JSON, no escribes nada más:

struct Publicacion: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
}

Para un JSON {"id": 1, "title": "Hola", "body": "…"}, este struct encaja directo. Si las claves difieren (JSON en snake_case), usas CodingKeys o un decoder con .convertFromSnakeCase.

La petición con URLSession

URLSession moderno es async: pides los datos con await y los tienes:

func cargarPublicaciones() async throws -> [Publicacion] {
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
    let (datos, respuesta) = try await URLSession.shared.data(from: url)

    guard let http = respuesta as? HTTPURLResponse, http.statusCode == 200 else {
        throw ErrorRed.codigoInvalido
    }
    return try JSONDecoder().decode([Publicacion].self, from: datos)
}

enum ErrorRed: Error { case codigoInvalido }

Tres pasos: pides los datos (await), compruebas el código HTTP, decodificas el JSON. El try propaga cualquier fallo (Nivel 1).

Conectarlo a la interfaz

Junta todo con el patrón .task y un modelo @Observable (Niveles 3 y 5):

@Observable
class Feed {
    var publicaciones: [Publicacion] = []
    var cargando = false
    var error: String?

    func cargar() async {
        cargando = true
        defer { cargando = false }
        do {
            publicaciones = try await cargarPublicaciones()
        } catch {
            self.error = "No se pudo cargar: \(error.localizedDescription)"
        }
    }
}

struct FeedView: View {
    @State private var feed = Feed()

    var body: some View {
        List(feed.publicaciones) { post in
            VStack(alignment: .leading) {
                Text(post.title).font(.headline)
                Text(post.body).font(.caption).foregroundStyle(.secondary)
            }
        }
        .overlay { if feed.cargando { ProgressView() } }
        .task { await feed.cargar() }
    }
}
Todo el curso encaja aquí

Detente a ver cuántas piezas se juntan en este ejemplo: Codable (Nivel 1) decodifica el JSON, async/await (5.1) hace la petición sin bloquear, @Observable (3.1) mantiene el estado, .task (5.1) lo carga al aparecer y lo cancela al salir, do/catch (1.5) maneja el error, y List (2.5) lo muestra. Esto no es un truco aislado: es cómo se construye de verdad la pantalla de datos de una app real. Cuando estas piezas te salen juntas y con naturalidad, ya piensas como un desarrollador iOS.

⚠️
Modela para lo que puede fallar

La red es el reino de lo incierto: no hay conexión, el servidor devuelve un error, el JSON viene raro. Un principiante asume el camino feliz; un profesional modela los tres estados —cargando, éxito, error— desde el principio (¿recuerdas los enums con valores asociados del Nivel 1?). Tu interfaz debe tener siempre respuesta para “está cargando” y “algo falló”, no solo para “todo bien”.

⚔️ Trae datos de internet
  1. Define un struct Codable que encaje con una API pública (prueba jsonplaceholder.typicode.com).
  2. Escribe una función async throws que descargue y decodifique el JSON.
  3. Crea un modelo @Observable con estados de carga y error.
  4. Muéstralo en una List cargando con .task.
  5. Añade un ProgressView mientras carga y un mensaje si falla.