Introducción
En el primer por sobre Swift hablamos de los tipos que existen destacando que en Swift no existe null. En Swift no existen las variables o constantes sin valor.
¿Que hacemos entonces si queremos declarar una variable con valor desconocido? ¿Y si una función falla y no devuelve nada?
¿Qué es un opcional?
Para resolver este, y muchos otros tipos de problemas Swift incorpora un modificador sobre las variables que es esencial al trabajar con este lenguaje: los opcionales.
Un valor opcional es un contenedor que puede tener dos valores: nada o algo. Mientras una variable o constante en Swift solo puede almacenar algo, pero nunca puede estar vacío; un opcional es un contenedor dentro de otro. Una variable opcional es como el gato de Schrödinger: puede que al abrir la caja en su interior encontremos un valor o puede que no. Hasta que no miremos dentro no lo sabremos.
Solo podemos utilizar opcionales con variables y no es posible inferir el tipo si las inicializamos vacías:
var hola = nil
Esto no funciona, debemos añadir el tipo:
var hola:String? = nil
Si te fijas, para convertir la variable en opcional simplemente añadimos una interrogación tras el tipo del dato.
Llegados a este punto es importante que entiendas que nil no es un valor válido para el tipo String, no podemos hacer:
var hola:String = nil
Al añadir la interrogación creamos un tipo nuevo que Swift trata de forma distinta. Un String opcional no es lo mismo que un String.
Si ahora modificamos el valor de esta variable opcional, vemos mejor cómo funcionan los opcionales por debajo:
var hola:String? = nil
hola = “Hola mundo”
print(hola)
Al hacer print vemos que no aparece “Hola mundo”, sino que aparece Optional(“Hola mundo”). Con esto podemos ver que nuestro valor está en realidad dentro de una caja, pero la lógica de Swift se encarga de que el proceso de asignación a esa “caja” sea transparente para nosotros.
¿Cómo accedemos entonces al valor?
Siguiendo con el ejemplo de la caja, si queremos obtener su valor debemos desempaquetar esa caja (unwrap en inglés, una palabra que leerás mucho aprendiendo Swift). Para ello podemos usar el símbolo ! que permite desempaquetar de manera implícita:
var hola:String? = nil
hola = “Adios”
print(hola!)
Al hacer ese cambio el resultado por consola ya no es un Optional. ¡Cuidado! Hacer un desempaquetado implícito o implicitly unwrap puede tener consecuencias desagradables. SOLO debemos usar ! cuando estemos seguros de que el opcional tiene contenido. Intentar acceder a un opcional vacío provocará un error en tiempo de ejecución:
var hola:String? = nil
print(hola!)
¿Cómo controlar entonces el valor de un opcional si no tenemos claro que tenga contenido? ¿Cómo saber si el gato está vivo o muerto sin abrir la caja? Para ello utilizaremos enlaces opcionales u “optional bindings”.
Enlaces opcionales
Una variable opcional puede tener dos valores:
- Optional(valor)
- nil
La ventaja de nil es que podemos utilizarlo en una condición booleana, es decir, si evaluamos un opcional y es nil nos devolverá false, y si tiene un dato que no sea vacío devolverá true. Esta acción no cambia el valor del opcional a ningún otro contexto impidiendo el fallo que hemos visto antes. No abrimos la caja, solo preguntamos por su estado: ¿Contiene un regalo?
Aprovechando esto podemos realizar un enlace opcional que asigna a una constante normal (no opcional ni variable) un valor opcional. Si el opcional tenía contenido, se ejecutará el ámbito dado pasando esa constante con el valor desempaquetado (unwrapped):
var regalo:String?
if let contenido = regalo{
print(“Cotiene \(contenido)”)
}else{
print(“No hay nada :(“)
}
Si el regalo es nil, la condición no se cumple y vamos directos al else.
var regalo:String?
regalo = “Un coche!”
if let contenido = regalo{
print(“Cotiene \(contenido)”)
}else{
print(“No hay nada :(“)
}
Pero, si por el contrario, tenemos un valor guardado, entraremos en el if con el valor desempaquetado en la constante contenido.
Tras entender cómo desempaquetar un opcional, al ir usándolos nos encontramos con un problema que se hizo famoso hasta que fue resuelto en Swift 2:
var regalo1:String?
var regalo2:String?
var regalo3:String?
if let regalo1 = regalo1{
if let regalo2 = regalo2{
if let regalo3 = regalo3{
}
}
}
Bonito, ¿verdad? Es lo que se conoce como “pirámide del mal”, una de las prácticas menos eficientes en programación. En Swift es posible resolver esto encadenando cada enlace separándolo por comas:
if let regalo1 = regalo1, let regalo2 = regalo2, let regalo3 = regalo3{
print(“todos los regalos tienen algo”)
}
También podemos añadir una condición tanto en los enlaces simples como en los anidados:
if let regalo1 = regalo1, regalo1 == “Coche”{
print(“Es un coche”)
}
if let regalo1 = regalo1, let regalo2 = regalo2, let regalo3 = regalo3, regalo3 == “Moto”{
print(“todos los regalos tienen algo y el último es una moto”)
}
En ambos casos, con múltiples enlaces opcionales hemos incluido let en cada enlace, pero es posible simplificar y reducir así la cantidad de código:
if let regalo1 = regalo1, regalo2 = regalo2, regalo3 = regalo3{
print(“todos los regalos tienen algo y el último es una moto”)
}
El enlace opcional se usa continuamente en Swift, es importante que lo aprendas bien y, sobre todo, que evites forzar el desempaquetado.
Control de flujo de opcionales
Al introducirnos en el mundo de Swift hablamos del concepto de ámbito. Un enlace opcional crea un nuevo ámbito y la constante desempaquetada sólo existe dentro de ese ámbito. Esto implica que el valor tras el if let sigue estando empaquetado:
var regalo:String?
regalo = “iPad pro”
if let regalo = regalo{
print(regalo)
}
print(regalo)
Para solucionar este problema en casos dónde sea necesario podemos usar el control de flujo “guard”, también llamado anti-if.
Lo que hace esta instrucción es romper la ejecución si no se da una condición. Su uso más común es prevenirnos frente a un error de desempaquetado. Con un ejemplo es más fácil de entender:
guard let regalo = regalo else {return}
Si el regalo está vacío, se ejecutará el ámbito con el return, impidiendo así que el código que queda por debajo se ejecute. En caso contrario, el código se ejecutará pero además contaremos con el regalo desempaquetado. Este ejemplo fallará en el playground, ya que no es posible hacer un return directamente en el ámbito principal del playground. En lugar de return podríamos utilizar break, continue e incluso throw para lanzar un error. Guard es una forma de asegurarnos antes de ejecutar el resto del código de que una condición se cumple. En muchos casos su uso se puede solapar con el del if, pero en este caso concreto de desempaquetado de un opcional en el propio ámbito se entiende muy bien su porqué.
Operador de coalescencia nula
No hablamos de este operador en el post sobre operadores porque está directamente relacionado con los opcionales. Es un operador muy sencillo representado por dos interrogaciones. Su función es desempaquetar un opcional y, en caso de que no tenga algo dentro, devolver un valor por defecto situado al lado derecho del operador:
var paquete:String?
let regalo = paquete ?? “Nada”
Si el paquete no trae un regalo me quedo con nada. Su resultado es similar a utilizar un operador ternario condicional evaluando si es nil y devolviendo en caso contrario el valor desempaquetado.
Encadenamiento de opcionales
Es muy posible que en nuestros desarrollos con Swift nos encontremos con la necesidad de acceder a una propiedad de un objeto opcional, o más complicado aún, a la propiedad de un objeto opcional que a su vez es propiedad de un objeto opcional… Solo la explicación ya marea y podemos imaginarnos la implementación como algo así:
if let propiedadA = propiedadA,
let propiedadB = propiedadA.propiedadB{
}
No queda especialmente bonito. Si la propiedad que necesitamos esta a 4 niveles (aunque acceder a tanta profundidad de un objeto ya no es recomendable) nos vamos a encontrar con una enorme cantidad de enlaces opcionales y un código muy difícil de seguir. Para evitar este tipo de problemas existe el encadenamiento de opcionales, transformando el caso anterior en este:
if let propiedadB = propiedadA?.propiedadB{
}
Al añadir la interrogación indicamos al compilador que solo debemos intentar evaluar propiedadB si propiedadA existe. También es posible utilizar el operador de desempaquetado implícito pero recuerda: solo debe usarse en casos concretos y siempre con la certeza de que ese dato existe. Siempre es mejor utilizar el operador condicional y conservar la seguridad que nos aporta Swift:
if let propiedadB = propiedadA!.propiedadB{
}
¿Por qué opcionales?
Muy posiblemente te estés preguntado ahora mismo si todo esto de los opcionales no es mucho lío. Seguramente pienses que sería mejor permitir utilizar el valor nil directamente como ocurre en otros lenguajes.
Podría ser más fácil, pero no más seguro y tampoco aplicable a todos los casos. El tipo opcional tiene una función muy clara: asegurar una mejor calidad del código por parte de los programadores y evitar errores mayores, sobre todo en tiempo de ejecución.
En otros lenguajes una variable vacía no es un error. Esto significa que, si nuestro programa tiene un error en su ejecución porque un objeto se queda vacío, será difícil de detectar ya que es posible que el error aparezca por otro lado. Swift transfiere la responsabilidad de gestionar bien las variables que puedan ser nil al desarrollador y obliga a utilizar mecanismos que permitan gestionar mejor casos no esperados, como encontrar una variable vacía que casi siempre no lo está. Por supuesto, es posible utilizar mal la herramienta y realizar desempaquetados implícitos por todas partes (!!!!!)… Pero aún así el error será más fácil de detectar.