Swift – Opcionales

Hoy aprenderemos sobre los tipos opcionales, un concepto propio y clave dentro del lenguaje Swift. Comprender los opcionales es fundamental, es una característica de uso diario, tanto en nuestro propio código como con UIKit o cualquier otro framework.

Pero antes respondamos la siguiente pregunta:

¿Qué es un opcional?

Un opcional en Swift es un tipo de dato que puede contener un valor o ningún valor. Los opcionales se definen agregando un símbolo de interrogación (?) al final de la anotación de tipo:

var middleName: String?

Como hay personas que no tiene middle name o segundo nombre, creo que es lógico que en cierto modelo de datos establezcamos este campo como opcional. Con lo cual informamos al compilador que esta variable puede que no contenga un valor asociado.

Imaginemos la variable middleName como un contenedor, una caja de la cual solo sabemos que puede almacenar valores de tipo String, pero no sabemos si actualmente almacena un valor o por el contrario está vacia, o mejor dicho es igual a nil.

¿Qué es Nil?

El valor especial nil (en minúsculas) establece un estado sin valor a una variable opcional.

var middleName: String?

if middleName == nil {
    
    print("La variable middleName es igual a nil!")
    
}

middleName = "Vladimir"

if middleName == nil {
    
    print("La variable middleName es igual a nil!")
    
} else {
    
    print("La variable middleName es igual al valor: \(middleName)")
    
}

Continuamos con nuestra variable middleName que al momento de declararla es igual a nil por defecto. Algo que comprobamos en el bloque if que hemos declarado a continuación. Acto seguido le asignamos un valor y nuevamente comproamos si esta es igual a nil, de lo contrario imprimimos el valor asociado.

La salida en pantalla sería la siguiente:

La variable middleName es igual a nil!
La variable middleName es igual al valor: Optional("Vladimir")

Optional

Seguramente muchos se preguntarán porque cuando imprimimos la variable middleName se muestra su valor como:

Optional("Vladimir")

Lo que sucede es que estamos viendo el envoltorio, la caja del ejemplo que les comenté. Cuando establecemos una variable como opcional y utilizamos la siguiente sintaxis (que es la recomendada):

var middleName: String?

Lo que estamos declarando es un contenedor, de tipo Optional, dentro del cual podrá haber un valor de tipo String. Por lo que el tipo String de existir siempre estará dentro de este envoltorio o wrapper (como se le llama en inglés).

Mientras que en una variable regular el valor de la misma se encuentra asociado directamente a esta y podemos acceder a él sin más. En el caso de las variables opcionales esto no es así, el posible valor de una variable opcional siempre se encuentra dentro de «la caja», dentro del contenedor, dentro del tipo Optional.

Así que de imprimirlo o nos devolverá nil o su valor asociado en formato Optional(VALOR).

Creo que podemos entender por qué Optional es un tipo especial. No es como Int o String, es de hecho un enum. Uno similar a este:

enum Optional<Wrapped> {
    
    case none
    case some(Wrapped)

} // Optional

Nota: Para los que tiene experiencia en otros lenguajes y saben de genéricos el Wrapped aquí es la T de toda la vida. Si por el contrario no sabes a que me refiero no te preocupes, es un tema avanzado que veremos más adelante.

Nota: Este enum es una versión simplificada del orirginal, es solo para que se entienda la lógica tras todo esto.

Mostrados los caso, que sí son fieles al original, cuando nuestra variable es igual a nil hacemos referencia al caso none y cuando tiene un valor asociado al caso some(Wrapped).

Por esta razón es que nuestra variable middleName también la podríamos inicializar de esta forma:

var middleName: Optional<String>

o también de esta otra:

var middleName = Optional<String>.none

si deseamos asociar un valor desde el inicio podemos hacerlo de la manera usual (y recomendada):

var middleName: String? = "Vladimir"

o también de esta otra:

var middleName = Optional.some("Vladimir")

Forced Unwrapping

Ahora, ¿qué hay si queremos acceder al valor como tal, al contenido de la caja sin tener que cargar con la misma? Esta pregunta es muy valida ya que con el valor empaquetado no podemos hacer mucho, por ejemplo:

var someNumer: Int? = 10

let result = someNumer + 5

Aquí lo que estamos haciendo es sumar dos valores, uno de ellos es opcional y el otro es un literal numérico, el resultado lo almacenamos en la variable result. Aunque luce como si todo estuviera bien si lo ejecutamos obtendremos un mensaje de error similar al siguiente:

error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'

El error nos informa de qué debemos desempaquetar la variable opcional de tipo Int? hacia un tipo Int no opcional. Básicamente porque en el ejemplo anterior lo que estamos intentando es sumar caja + 5, cuando lo que realmente deseamos es sumar 10 + 5.

Entonces sí queremos trabajar con el valor que almacenamos en la caja, en el contenedor o wrapper, primero tenemos que sacarlo de ella.

Para esto existen varios enfoques, unos más seguros y otros más inseguros y no recomendados. Este último sería el force unwrapping o desempaquetamiento forzado si lo traducimos al castellano, ¿se dan cuenta por qué uso el nombre en inglés?

El force unwrapping es la manera más directa de acceder al valor de una variable opcional y lo hacemos mediante el signo de fin admiración («!»), siguiendo esta sintaxis:

<VARIABLE OPCIONAL>!

tal y como podemos observar en este ejemplo:

var middleName: String?

middleName = "Vladimir"

print(String(describing: middleName!))

la salida en pantalla sería:

Vladimir

nótese que hemos accedido al valor como tal y no al anterior Optional(«Vladimir»).

¿Por qué es peligroso?

Que sucedería si modificásemos el último ejemplo al siguiente:

var middleName: String?

print(String(describing: middleName!))

obtendríamos un error como este:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

El mensaje de error nos informa que ha ocurrido un error fatal mientras se desempaquetaba un valor opcional, se ha encontrado nil de manera inesperada en lugar del valor esperado, en este caso String.

Por esta razón es que no se recomienda su uso, y es que por equivocación o por algun error lógico una variable opcional puede eventualmente ser igual a nil y hacer que nuestra aplicación deje de funcionar, algo que siempre debemos evitar.

Optional Binding

Al igual que el desempaquetamiento forzado (forced unwrapping), el optional binding (o enlace opcional en castellano) es una forma mucho más segura de acceder a los valores dentro del contenedor (la caja).

Veamos un ejemplo:

var middleName: String?

middleName = "Vladimir"

if let safeMiddleName = middleName {
    
    print("El segundo nombre del usuario es: \(safeMiddleName)")
    
} else {
    
    print("El usuario no tiene segundo nombre!")
    
}

El optional binding toma lugar en el if, y como pueden observar lo hace de una forma bastante singular. La condición de este if reside en que se pueda definir una constante apartir del valor que podría almacenar la variable opcional, caso contrario se ejecutaría el bloque else.

Este bloque if-let-else se puede leer de la siguiente forma:

Si la variable opcional middleName tiene un valor asociado define la constante safeMiddleName con el valor de middleName. De lo contrario es igual a nil, siendo así ejecuta el bloque else.

Sobre el ejemplo anterior.

Nil Coalescing

Hay ocaciones en las que necesitaremos manejar un valor por defecto en caso de que una variable opcional sea igual a nil. Para lograr esto lo primero que nos viene a la mente sería algo así:

var middleName: String?

if let safeMiddleName = middleName {
    
    middleNameTextField.text = middleName
    
} else {
    
    middleNameTextField.text = "Middle name"
    
} // else

print(middleName)

Hemos modificado un poco el ejemplo anterior y establecemos un escenario donde estamos rellenando los datos del usuario en el clásico formulario donde el usuario puede editar sus datos.

En este caso solo nos referimos al campo de segundo nombre (middleNameTextField) al cual asignamos un placeholderen en caso de la variable opcional (middleName) ser igual a nil,.

Nota: En un entorno real un objeto UITextField tendría un placeholder asociado por defecto desde un inicio y no precisamente en el atributo text, para este fin contamos con el atributo placeholder. Este ejemplo es solo con fines didácticos.

Este enfoque soluciona el problema, pero aún así podemos abogar por una mejor alternativa mediante el operador ternario, de esta manera:

middleNameTextField = middleName != nil ? middleName! : "Middle name"

Si bien es cierto que hemos logrado lo mismo en una sola línea, Swift nos ofrece una opción mucho más óptima. Me refiero al nil coalescing operator o operador de coalescencia nil:

middleNameTextField = middleName ?? "Middle name"

Mediante el nil coalescing operator logramos lo mismo pero de una manera mucho más compacta y conscisa, más legible. Si middleName es igual a nil, se retorna el valor que hemos especificado a la derecha, de lo contrario el valor asociado a la variable opcional.

Optional Chaining

El encadenamiento opcional (o también optional chaining) es una característica que nos permite acceder a propiedades, métodos y subíndices de objetos que son opcionales, con lo cual podrían ser igual a nil.

A diferencia del desempaquetamiento forzado (forced unwrapping) que da lugar a una excepción en tiempo de ejecución cuando se encuentra con nil, el optional chaining ha sido diseñado para sobrevivir a nil y es sumamente util. Veamos:

struct Person {
    
    let name: String
    
    var car: Car?
    
} // Person

struct Car {
    
    let brand: String
    
    var vehicleInspection = false
    
} // Car

let client = Person(name: "Oscar", car: nil)

if let inspection = client.car?.vehicleInspection {
    
    if inspection {
        
        print("INFO: El cliente tiene carro y ha hecho la inspección del mismo.")
        
    } else {
        
        print("ADVERTENCIA: El cliente tiene carro y no ha hecho la inspección del mismo.")
        
    } // else

} else {
    
    print("INFO: El cliente no tiene carro!")
    
} // else

En este ejemplo planteamos un escenario donde tenemos dos estructuras, una referente a una persona y otra a un carro. Una persona puede o no tener un carro así que dentro de la estructura Person incluímos el carro como opcional. El carro por su parte incluye una propiedad boleana donde establecemos si el ha carro ha pasado la revisión técnica o no.

En otro segmento de nuestro código nos surge la necesidad de verificar, si el carro del cliente ha pasado la revisión.

¿Qué sucedería si queremos comprobar el valor de vehicleInspection pero el cliente no tiene carro?

Si el cliente no tiene carro nos encontraremos con un nil al tratar de acceder a la propiedad que deseamos, porque de hecho no existirá instancia alguna.

Para tener esta posibilidad bajo control tenemos el optional chaining en Swift, y este consiste en anteponer el signo de interrogación (?) al punto que nos da acceso a las propiedades y métodos de la instancia.

client.car?.vehicleInspection

Cada vez que veamos algo así ya sabemos que car (en este caso) es opcional y el signo de interrogación nos ayuda a validar el estado actual de car. Si el cliente no tiene carro pues car será igual a nil y este es el valor que se retorna, en casi de tener un objeto Car asociado entonces podemos seguir la cadena al próximo eslavón (vehicleInspection en este caso).

Conclusiones

Creo que se ha entendido todo bastante bien, no obstante todo es cuestión de práctica y los Opcionales son una caracteristica de uso diario en Swift y poco a poco se volverán algo bien natural.

Los Opcionales brindan flexibilidad al lenguaje y cuando decimos que Swift es un lenguaje con un enfoque en la seguridad, los Opcionales también son parte de esto.

Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo y síguenos en nuestras redes sociales. Mantente al tanto de todas nuestras publicaciones.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

¡Cualquier duda o sugerencia, ya sea errores a corregir o ejemplos a añadir, será más que bienvenida, necesaria!

Deja una respuesta

Su dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para mejorar su experiencia. Asumiremos que está de acuerdo con esto, pero puede optar por no participar si lo desea. Aceptar Leer Más

RECIBE CONTENIDO SIMILAR EN TU CORREO

RECIBE CONTENIDO SIMILAR EN TU CORREO

Suscríbete a nuestra lista de correo y mantente actualizado con las nuevas publicaciones.

Se ha suscrito correctamente!