Errores personalizados con throws en Swift

Resumen

Crear errores personalizados en Swift te permite manejar fallos específicos de tu sistema con mensajes claros y accionables. Aprenderás a definir un grupo de errores con enum, implementar los protocolos Error y LocalizedError, y lanzarlos con throws desde tus métodos para capturarlos después.

Esta guía es útil si ya manejas funciones, protocolos y enumerables en Swift, y quieres dar el salto a un manejo de errores profesional dentro de un sistema real, como un gestor de estudiantes.

Cómo defines un enum de errores personalizados en Swift

Todo arranca con un archivo nuevo, en este caso ManagerError, que agrupa los errores que tu sistema puede lanzar. La idea es que cada caso represente una falla concreta y sea fácil de leer cuando aparezca en consola o en pantalla [00:24].

Para construirlo, declaras un public enum que adopta dos protocolos: Error, que ya viene en Swift, y LocalizedError, que te deja escribir descripciones más ricas para cada caso.

Los casos del enum representan situaciones reales del gestor de estudiantes:

  • studentNotAddedError: cuando no puedes agregar un estudiante.
  • subjectNotAssignedError: cuando no puedes asignar una materia.
  • reportNotFoundError: cuando la lista de estudiantes está vacía y no hay datos para reportar.
  • maxStudentsReachedError(max: Int): cuando ya alcanzaste el límite de estudiantes.

Fíjate en el último caso. Recibe un parámetro asociado de tipo Int, algo que muchas personas no saben que los enums de Swift pueden hacer. Ese valor lo vas a usar después para construir un mensaje dinámico.

¿Qué es LocalizedError en Swift? Es un protocolo que extiende a Error y te permite definir una propiedad errorDescription opcional. Sirve para mostrar mensajes legibles al usuario en lugar de errores genéricos del sistema.

Cómo escribir descripciones claras con errorDescription

Una vez que tu enum adopta LocalizedError, implementas la variable public var errorDescription: String?. Es opcional porque así lo define el protocolo, y dentro haces un switch self para devolver el mensaje correcto según el caso [02:30].

swift public enum ManagerError: Error, LocalizedError { case studentNotAddedError case subjectNotAssignedError case reportNotFoundError case maxStudentsReachedError(max: Int)

public var errorDescription: String? { switch self { case .studentNotAddedError: return "El estudiante no se pudo agregar" case .subjectNotAssignedError: return "No se pudo asignar la materia" case .reportNotFoundError: return "El reporte no se pudo encontrar, puesto que la lista de estudiantes está vacía" case .maxStudentsReachedError(let max): return "La cantidad máxima de estudiantes es \(max) y ya fue alcanzada" } }

}

Entre más descriptivos sean tus mensajes, mejor experiencia tendrá quien use tu app. En el caso del último error, accedes al valor asociado con let max dentro del case, lo que te permite incrustarlo en el texto.

Por qué usar valores asociados en los casos del enum

El valor asociado convierte un error estático en un error con contexto. En vez de decir solo "alcanzaste el máximo", puedes decir exactamente cuántos estudiantes son el límite. Eso le da al usuario información accionable sin que tengas que crear un caso distinto por cada cantidad posible.

Cómo lanzar errores con throws en tus métodos

Para que un método pueda lanzar errores, necesitas marcarlo con la palabra throws tanto en el protocolo como en su implementación. En el StudentsManager, los métodos insertStudent, assignSubjectToStudent y generateReport se modifican para incluir esta marca [05:10].

¿Qué hace la palabra throws en Swift? Indica que una función puede lanzar un error en lugar de retornar normalmente. Quien la llame estará obligado a manejar ese error con try, do-catch o propagándolo.

En insertStudent, antes existía una validación que retornaba si el estudiante era nulo. Ahora ese return se reemplaza por throw ManagerError.studentNotAddedError. Pero hay una segunda validación que tienes que añadir: el límite máximo de estudiantes.

Para eso, agregas una propiedad nueva al StudentsManager llamada maxStudents, de tipo Int, y la inicializas en el constructor:

swift class StudentsManager: StudentsManagerProtocol { var students: [Student] = [] var maxStudents: Int

init(maxStudents: Int) { self.maxStudents = maxStudents } func insertStudent(_ student: Student?) throws { guard let student = student else { throw ManagerError.studentNotAddedError } if students.count < maxStudents { students.append(student) } else { throw ManagerError.maxStudentsReachedError(max: maxStudents) } }

}

Cómo aplicas throws en asignar materias y generar reportes

Para assignSubjectToStudent, dentro del bloque else que detecta que el estudiante no existe, lanzas throw ManagerError.subjectNotAssignedError.

Para generateReport, antes de iterar la lista, validas si está vacía con if students.isEmpty y lanzas throw ManagerError.reportNotFoundError. Si hay estudiantes, sigues con el comportamiento normal de imprimir cada descripción.

Al instanciar el StudentsManager en el playground, ahora tienes que pasarle el límite, por ejemplo StudentsManager(maxStudents: 3). Eso resuelve el primer error de compilación, pero verás que aparecen muchos más [10:45]. Esos nuevos errores te avisan que los métodos marcados con throws no están siendo manejados todavía por quien los llama.

Ese manejo con do-catch y try es justo lo que viene en la siguiente clase. ¿Ya intentaste implementar tus propios errores personalizados en algún proyecto? Cuéntame en los comentarios qué casos de uso le diste.