Tal como dije en mi último artículo del 2015, una de mis prioridades de 2016 es aprender Swift. Creo que lo mejor para eso es ir registrando en mi blog lo que voy descubriendo y aprendiendo. Seguro que a alguien podrá serle de utilidad y además a mi me ayudará a asentar los conocimientos que vaya adquiriendo.
Esta serie de artículos que empieza aquí tratará de Swift y de lo que iré aprendiendo.
¿Qué es un opcional?
La primera vez que intenté hacer algo con Swift, una de las primeras cosas que me echó para atrás fueron los opcionales. En ese momento, no entendí ni su significado, ni su funcionamiento ni mucho menos su utilidad. No estaba concentrado posiblemente y al ver ese nuevo concepto que para mi era completamente nuevo, decidí dejar de lado Swift y programar mi app con el viejo Objective C.
Empecemos con un ejemplo:
1 |
var nombre: String? |
«nombre» es una variable del tipo “String?”, es decir “Optional
Para utilizar esta variable, primero debemos extraer («unwrap») su valor. No es posible realizar ninguna acción que se realiza con un «String» antes de realizar el unwrap.
Por supuesto, es un error definir una constante como un opcional utilizando “let”. Una constante no puede ser de un tipo opcional, ya que una constante sólo puede contener un valor. Un opcional puede contener algo o puede ser nil. Definir un opcional como una constante provocará un error en tiempo de compilación.
¿Cómo se utiliza un opcional?
Existen varias técnicas para utilizar un opcional. No puedo traducir la mayoría de las expresiones porque la verdad no sabría traducir algunas palabras. De todas formas, la mayor parte de la documentación de Swift (y de programación en general) está en inglés.
Conditional binding
1 2 3 4 5 6 |
var nombre: String? if let nombreExtraido = nombre { print("nombre: \(nombreExtraido)") } else { print("nombre es nil.") } |
Este ejemplo muestra como extraer el valor utilizando una técnica llamada “Conditional binding”. Primero asignamos la variable a una constante y si esta constante no es «nil», la utilizamos. En caso de ser nil, mostramos un mensaje indicando que la variable es «nil». Esta manera es la que más he utilizado hasta hoy sin duda.
Forced unwrapping
1 2 3 |
var nombre: String? nombre = "Miguel" print("nombre: \(nombre!)") |
Esta técnica se utiliza sólo cuando estás completamente seguro de que la variable «nombre» tiene efectivamente un valor. Al utilizar el modificador “!” estás forzando la extracción de la variable. Esto sólo funciona si la variable tiene un valor. Si la variable no tiene valor, esto provocaría un error en tiempo de ejecución. Esta técnica puede resultar peligrosa por lo tanto.
Nil coalescing
1 2 3 |
var nombre: String? let nombreNuevo = nombre ?? "Miguel" print("nombreNuevo: \(nombreNuevo)") |
Vaya nombrecito… Esta técnica me recuerda al operador condicional y ternario «?» de php. Consiste en algo muy parecido. Comprueba si el opcional tiene un valor y si es así se lo asigna a tu constante, si no tiene valor, asigna el valor que tú indiques.
En el caso de mi ejemplo, la constante «nombreNuevo» contendrá el valor de la variable “nombre” sólo si esta contiene un valor. En cambio si no contiene nada «nil», la constante «nombreNuevo» contendrá el valor “Miguel”. En los dos casos, la constante resultante será del tipo “String” y no será un opcional.
Optional chaining
La última técnica para extraer un valor de un opcional es utilizando Optional chaining. Esta técnica es muy utilizada cuando estás tratando con objetos Veamos el siguiente ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Post { var autor: Persona? } class Persona { var nombre: String? } var p: Post? [...] // Aquí ocurren cosas maravillosas. let nombreAutor = p?.autor?.nombre print("nombreAutor: \(nombreAutor)") |
«nombreAutor» contendrá el nombre del autor del artículo “p” sólo si se cumplen las siguientes condiciones:
- «p» contiene un valor y no es «nil». Este valor debe ser un objeto del tipo “Post”.
- El autor de «p» contiene un valor y no es «nil». Este valor debe ser un objeto del tipo “Persona”.
- El nombre del autor contiene un valor y no es «nil». Este valor debe ser un «String».
En lugar de las […], escribo el siguiente código:
1 2 3 4 5 |
var a: Autor? // a es nil. p = Post() // p ya no es nil. a = Persona() // a ya no es nil. p?.autor = a // el autor de p ya no es nil, es a. a?.nombre = "Miguel" // el nombre del autor a ya no es nil, es "Miguel". |
Primero, defino una variable «a» del tipo opcional «Autor». Después inicializo «p» con el constructor por defecto. Ahora mismo «p» ya no es «nil», contiene un valor, pero el autor de «p» sigue siendo «nil». Por lo tanto, «nombreAutor» seguirá siendo «nil».
Después inicializo el autor «a» con el constructor por defecto de «Persona». Ahora, «a» ya no es «nil», pero el nombre sigue siéndolo. Por lo tanto, la última condición sigue sin cumplirse así que «nombreAutor» sigue siendo «nil».
Finalmente, asigno la cadena «Miguel» a la propiedad «nombre» de «a». Por lo tanto, ahora sí, el valor de «nombreAutor» será «Miguel».
El código todo junto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Post { var autor: Persona? } class Persona { var nombre: String? } var p: Post? // p es nil. var a: Autor? // a es nil. p = Post() // p ya no es nil. a = Persona() // a ya no es nil. p?.autor = a // el autor de p ya no es nil, es a. a?.nombre = "Miguel" // el nombre del autor a ya no es nil, es "Miguel" let nombreAutor = p?.autor?.nombre // Si p no es nil Y el autor de p no es nil, se asigna el valor del nombre del autor de p. print("nombreAutor: \(nombreAutor)") // nombreAutor: Miguel |
Implicitly unwrapped optional
1 2 3 |
var nombre: String! // Estoy seguro de que nombre tiene valor. nombre = "Miguel" // Asigno un valor. print("nombre: \(nombre)") // Si no tiene valor, tendré problemas. |
¿Qué significa esto? En este caso, estamos definiendo una variable opcional «String» pero indicando que contiene un valor y por lo tanto no es necesario extraerla. Es decir puedes utilizarla directamente como si fuera un String ahorrándote las técnicas para extraer el código.
El problema es que si tu variable no contiene valor, tendrás un error en tiempo de ejecución, así que ten mucho cuidado al utilizar este método.
Podrás ver que esta técnica se utiliza con los «IBOutlet», al conectar elementos de tu vista con tu controlador. Esto es porque estos «IBOutlet» son «nil» hasta el momento en el que aparecen en pantalla. Por lo tanto en el momento en el que tu los vas a utilizar y trabajar con ellos (viewDidLoad, viewWillAppear, …), no serán «nil». Pero en el momento de inicializar el controlador, estos elementos serán «nil». Sólo después de cargar la vista, tendrán un valor.
¿Por qué usar un opcional?
Aunque al principio, suena raro y no lo entiendes del todo, poco a poco irás descubriendo que es muy cómodo trabajar con opcionales. La mejor manera de verlo creo que es al utilizar Optional chaining.
Volviendo al código de ejemplo que he puesto más arriba, lo volveré a escribir ahora sin utilizar opcionales y confrontaré las dos versiones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Post { var autor: Persona // Sin opcional, es obligatorio tener constructor. init(autor: Persona) { self.autor = autor } } class Persona { var nombre: String = "" // En este caso, usaré valor por defecto. } var a = Persona() var p = Post(autor: a) p.autor = a a.nombre = "Miguel" let nombreAutor = p.autor.nombre |
En este caso, estoy obligado a definir valores por defecto o constructores ya que no puedo usar «nil» al no usar opcionales. Cuando creo un objeto Post, tengo que pasarle el objeto Persona. Si se puede dar el caso de que un objeto Post no tenga autor, entonces no puedo asignarlo a «nil». Para utilizar «nil», tengo que utilizar opcionales.
En Objective-C, lo que hacías era comprobar si tu variable era «nil» antes de utilizarlo o no? No! En Objective-C, podías programar sin preocupaciones hasta que un buen día tu app dejaba de funcionar porque la variable que tú pensabas debía contener algo, contiene «nil». Sé sincero y reconoce que has pasado horas y horas mirando tu código sin entender por qué había fallado esto, hasta que te das cuenta que existe un remoto caso que tú no habías previsto en el que esa variable que tú pensabas que tenía que traer siempre un valor, de repente es «nil». Los opcionales terminan con todo esto al menos en parte. Al tener que extraer el valor de las variables no podrás tener ese error. Sólo si te gusta vivir peligrosamente utilizando el método implícito podrás seguir cometiendo esos mismos errores.