Manejo de errores en Swift: try/catch y más
En el desarrollo moderno de iOS y macOS, la confiabilidad de una aplicación es uno de los indicadores clave de calidad. Un manejo adecuado de errores en Swift no solo permite evitar fallos inesperados, sino también hacer que el código sea más legible y predecible. En este artículo, analizaremos no solo el mecanismo clásico try/catch, sino también enfoques alternativos, incluyendo el tipo Result, el operador defer y patrones avanzados.
Fundamentos: el protocolo Error y la palabra clave throws
En Swift, los errores están representados por tipos que cumplen con el protocolo Error. La mayoría de las veces se utilizan enumeraciones (enum) para esto. Para que una función pueda lanzar un error, se usa la palabra clave throws en su declaración.
enum NetworkError: Error { case noInternet case serverError(code: Int) case timeout}
func fetchData(from url: String) throws -> Data { guard url.hasPrefix("https") else { throw NetworkError.serverError(code: 400) } // ... simulación de carga return Data()}Tenga en cuenta: si una función está marcada con throws, el código que la llama está obligado a manejar el posible error usando try, try? o try!.
La construcción do-catch: el enfoque clásico
La forma más común de manejo es el bloque do-catch. Coloca el código potencialmente peligroso dentro de do, y en catch describe qué hacer con cada error específico.
do { let data = try fetchData(from: "http://example.com") print("Datos recibidos: \\(data)")} catch NetworkError.noInternet { print("No hay conexión a internet")} catch NetworkError.serverError(let code) { print("Error del servidor con código: \\(code)")} catch { print("Error desconocido: \\(error.localizedDescription)")}Importante: el último bloque catch sin patrón captura todos los errores restantes. Esta es una buena práctica para no pasar por alto excepciones inesperadas.
try? y try!: alternativas rápidas
Si no le importa el tipo de error, sino que solo necesita saber si la función se ejecutó correctamente, use try?. En caso de error, el resultado será nil.
if let data = try? fetchData(from: "https://valid.com") { print("Éxito: \\(data)")} else { print("Error al cargar")}El operador try! es una «bomba de tiempo». Suprime el manejo de errores y provoca un bloqueo de la aplicación si ocurre un error. Úselo solo en casos en los que esté 100% seguro de que no hay error (por ejemplo, al cargar un recurso integrado).
let data = try! fetchData(from: "https://trusted-source.com")// Si hay error, la aplicación fallaráEl tipo Result: enfoque funcional
A partir de Swift 5, se agregó el tipo Result<Success, Failure> a la biblioteca estándar. Es un contenedor que puede contener un valor exitoso o un error. Este enfoque es especialmente conveniente en código asíncrono y en funciones de devolución de llamada (completion handlers).
func fetchDataResult(from url: String) -> Result<Data, NetworkError> { guard url.hasPrefix("https") else { return .failure(.serverError(code: 400)) } return .success(Data())}
// Usolet result = fetchDataResult(from: "http://bad.com")switch result {case .success(let data): print("Datos: \\(data)")case .failure(let error): print("Error: \\(error)")}La ventaja de Result es que no olvida manejar el error, ya que el compilador lo obliga a