En el lenguaje Swift, el mecanismo de conversión de tipos aborda la tarea de verificar y transformar un valor de un tipo a otro en tiempo de ejecución. Sus raíces se encuentran en la tipificación estática y la herencia: en Swift se puede trabajar simultáneamente con tipos de valor (struct/enum) y tipos de referencia (class/protocol). El problema surge cuando un objeto o valor se asigna a una variable del tipo Any o protocolo, y se requiere saber si se puede convertir a otro tipo (generalmente más específico) sin riesgo de que la aplicación se bloquee.
La conversión segura de tipos permite aprovechar las ventajas de la seguridad de tipos, el polimorfismo dinámico y protege contra errores al trabajar con colecciones mixtas o jerarquías de clases.
Swift proporciona tres formas de conversión de tipos:
as — conversión segura (upcast), cuando el compilador siempre conoce el resultado por adelantadoas? — conversión condicional (optional) (downcast), devuelve optional si no se pudo convertiras! — conversión forzada (forced), provoca un crash en tiempo de ejecución si no se puede convertirEjemplo de código:
class Animal {} class Dog: Animal { func bark() { print("¡Guau!") } } let animals: [Animal] = [Dog(), Animal()] for animal in animals { if let dog = animal as? Dog { dog.bark() // conversión segura con as? } else { print("¡No es un perro!") } }
Características clave:
¿Se puede usar el operador as! para la conversión entre tipos no relacionados (por ejemplo, de String a Int)? ¿Cómo terminará esto?
El operador as! siempre provoca un crash en tiempo de ejecución si los tipos son incompatibles o si no hay herencia o una jerarquía de protocolos comunes entre ellos. Esto es inaceptable para la conversión entre tipos fundamentalmente diferentes.
Ejemplo de código:
let value: Any = "abc" let num = value as! Int // crash: No se pudo convertir el valor del tipo 'String' a 'Int'
¿Qué sucederá si se usa as? al convertir a un tipo con el que no hay ninguna jerarquía?
as? siempre devuelve nil si no es posible realizar la conversión de manera segura, incluso si los tipos no están relacionados por herencia o protocolos implementados.
Ejemplo de código:
let value: Any = 5 if let str = value as? String { print(str) } else { print("No se puede convertir a String") // Esta rama será la que se ejecute }
¿Se puede usar as para conversiones descendentes en jerarquías de clases (downcasting)?
No. El operador as solo se aplica a upcasts (por ejemplo, de Dog a Animal, o al convertir a un protocolo implementado). Para conversiones descendentes siempre se utilizan as? o as!.
Ejemplo de código:
let animal: Animal = Dog() // let dog = animal as Dog // Error en tiempo de compilación let dog = animal as? Dog // Forma correcta
En el proyecto había una colección [Any], donde por error se añadían cadenas y números. Para procesar los datos, el desarrollador en varios lugares escribió: let value = item as! String, lo que provocaba crashes al encontrar un número en el array.
Ventajas:
Desventajas:
Se reescribió utilizando tipos asociados en enum:
enum Payload { case text(String); case number(Int) } let data: [Payload] = [.text("abc"), .number(1)]
Ventajas:
Desventajas: