En este módulo, profundizaremos en el manejo avanzado de errores en Swift. Aprenderemos cómo definir y lanzar errores personalizados, cómo utilizar try? y try!, y cómo trabajar con errores en funciones asíncronas. Este conocimiento es crucial para escribir código robusto y manejable.

Contenido

Definición de Errores Personalizados

En Swift, los errores se definen mediante enumeraciones que adoptan el protocolo Error. Esto permite crear tipos de error personalizados que se pueden lanzar y capturar.

enum NetworkError: Error {
    case badURL
    case requestFailed
    case unknown
}

Explicación

  • NetworkError es una enumeración que adopta el protocolo Error.
  • Cada caso de la enumeración representa un tipo específico de error que puede ocurrir.

Lanzamiento y Captura de Errores

Para lanzar un error, se utiliza la palabra clave throw. Para capturar errores, se utiliza una estructura do-catch.

func fetchData(from url: String) throws {
    guard url == "https://valid.url" else {
        throw NetworkError.badURL
    }
    // Simulación de una solicitud de red
    let success = false
    if !success {
        throw NetworkError.requestFailed
    }
}

do {
    try fetchData(from: "https://invalid.url")
} catch NetworkError.badURL {
    print("URL inválida.")
} catch NetworkError.requestFailed {
    print("La solicitud falló.")
} catch {
    print("Ocurrió un error desconocido.")
}

Explicación

  • fetchData(from:) es una función que puede lanzar errores (throws).
  • Se utiliza guard para verificar la validez de la URL y lanzar un error si es inválida.
  • En el bloque do, se intenta ejecutar fetchData(from:) y se capturan los errores específicos en los bloques catch.

Uso de try? y try!

Swift proporciona dos formas adicionales de manejar errores: try? y try!.

try?

Convierte el resultado en un valor opcional. Si la función lanza un error, el resultado será nil.

let data = try? fetchData(from: "https://invalid.url")
if data == nil {
    print("Error al obtener los datos.")
}

try!

Asume que la función no lanzará un error. Si lo hace, el programa se detendrá.

let data = try! fetchData(from: "https://valid.url")
print("Datos obtenidos con éxito.")

Explicación

  • try? es útil cuando no te importa el error específico y solo necesitas saber si la operación tuvo éxito.
  • try! debe usarse con precaución, ya que puede causar que el programa se detenga si se lanza un error.

Propagación de Errores

Los errores pueden propagarse a través de múltiples niveles de funciones. Esto se hace declarando que una función puede lanzar errores (throws) y utilizando try al llamar a otras funciones que también pueden lanzar errores.

func performNetworkRequest() throws {
    try fetchData(from: "https://valid.url")
}

do {
    try performNetworkRequest()
} catch {
    print("Error durante la solicitud de red: \(error)")
}

Explicación

  • performNetworkRequest() llama a fetchData(from:) y propaga cualquier error que se lance.
  • En el bloque do, se captura cualquier error lanzado por performNetworkRequest().

Errores en Funciones Asíncronas

En Swift, las funciones asíncronas pueden manejar errores utilizando el mismo enfoque de throws y try.

func fetchDataAsync(from url: String, completion: @escaping (Result<String, Error>) -> Void) {
    DispatchQueue.global().async {
        do {
            try fetchData(from: url)
            completion(.success("Datos obtenidos con éxito."))
        } catch {
            completion(.failure(error))
        }
    }
}

fetchDataAsync(from: "https://invalid.url") { result in
    switch result {
    case .success(let data):
        print(data)
    case .failure(let error):
        print("Error: \(error)")
    }
}

Explicación

  • fetchDataAsync(from:completion:) es una función asíncrona que utiliza un bloque de finalización (completion) para manejar el resultado.
  • Utiliza DispatchQueue.global().async para ejecutar la solicitud de red en un hilo de fondo.
  • El bloque de finalización recibe un Result que puede ser .success o .failure.

Ejercicios Prácticos

Ejercicio 1: Definir y Lanzar Errores Personalizados

Define una enumeración de errores personalizados para una función que valida direcciones de correo electrónico. Implementa la función y lanza errores apropiados.

enum EmailError: Error {
    case invalidFormat
    case domainNotAllowed
}

func validateEmail(_ email: String) throws {
    guard email.contains("@") else {
        throw EmailError.invalidFormat
    }
    guard email.hasSuffix("@example.com") else {
        throw EmailError.domainNotAllowed
    }
}

do {
    try validateEmail("[email protected]")
} catch EmailError.invalidFormat {
    print("Formato de correo electrónico inválido.")
} catch EmailError.domainNotAllowed {
    print("Dominio no permitido.")
} catch {
    print("Ocurrió un error desconocido.")
}

Ejercicio 2: Uso de try? y try!

Modifica la función validateEmail para usar try? y try!.

let emailValidation1 = try? validateEmail("[email protected]")
if emailValidation1 == nil {
    print("Error al validar el correo electrónico.")
}

let emailValidation2 = try! validateEmail("[email protected]")
print("Correo electrónico válido.")

Ejercicio 3: Propagación de Errores

Crea una función que llame a validateEmail y propague cualquier error que se lance.

func checkEmail(_ email: String) throws {
    try validateEmail(email)
}

do {
    try checkEmail("[email protected]")
} catch {
    print("Error al verificar el correo electrónico: \(error)")
}

Conclusión

En esta sección, hemos explorado el manejo avanzado de errores en Swift. Aprendimos a definir y lanzar errores personalizados, utilizar try? y try!, propagar errores y manejar errores en funciones asíncronas. Estos conceptos son fundamentales para escribir código Swift robusto y manejable. En el próximo módulo, profundizaremos en la gestión de memoria en Swift.

© Copyright 2024. Todos los derechos reservados