UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Opcionales (Optionals)

Swift es un lenguaje de programación bastante seguro, me refiero a que ha sido creado para asegurarse de que tu código no falle de forma sorpresiva.

Una las formas más comunes en las que tu código puede fallar es cuando intentas utilizar datos que no son correctos o no existen. Por ejemplo, imagina una función como esta:

func getHaterStatus() -> String {
    return "Hate"
}

Esa función no acepta ningún parámetro y devuelve un cadena de caracteres: "Hate", sin embargo, si hoy está particularmente soleado, y los haters no se sienten motivados a criticar - ¿qué hacen entonces?, no queremos devolver nada: Este hater no criticará hoy.

Ahora, cuando se trata de una cadena de caracteres podrías pensar que una cadena vacía es una buena forma de comunicar "nada", y esto puede ser cierto en algunos casos, sin embargo, ¿qué sucede con los números?, ¿es 0 "un número vacío"? o ¿lo es -1?.

Antes de que comiences a crear reglas imaginarias, Swift ofrece una solución: optionals. Una variable opcional es aquella que puede o no tener un valor. Mucha gente considera a los opcionales difíciles de comprender, y está bien - Voy a intentar explicarlos de diferentes maneras, esperando que alguna de ellas te permita entenderlo.

Por ahora, imagínate que entrevistas a alguien y le preguntas "En una escala del 1 al 5, ¿qué tan increíble es Taylor Swift?", ¿qué respondería alguien que nunca ha oido de ella?, 1 seguramente será injusto y 5 sería exagerado cuando no sabe quién es Taylor Swift. La solución son los opcionales: "No quiero proporcionar un número en absoluto".

Cuando utilizamos -> String quiere decir "esto, definitivamente, va a devolver una cadena de caracteres", que a su vez quiere decir que esta función no puede devolver un valor vacío, por tanto se puede llamar de forma segura sabiendo que siempre devolverá un valor que puedes utilizar como una cadena de caracteres. Si queremos decirle a Swift que esta cadena puede o no devolver un valor debemos crearla de la siguiente forma:

func getHaterStatus() -> String? {
    return "Hate"
}

Fíjate en el signo de interrogación adicional: String? que quiere decir "cadena de caracteres opcional". Bien, en nuestro caso queremos seguir devolviendo "Hate" no importa que, sin embargo, modifiquemos la función un poco: si el día es soleado, los haters han decidido empezar una nueva vida y han decidido dejar de criticar, entonces queremos devolver "nada". En Swift, este "nada" tiene un nombre especial: nil.

Modifiquemos la función de esta forma:

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

Esta función acepta ahora un parámetro (el clima) y devuelve una cadena de caracteres (critica), sin embargo, ese valor a devolver puede estar o no, es decir, puede ser nil. En este caso, quiere decir que podemos obtener una cadena de caracteres o podemos obtener nil al llamar esta función.

Ahora vamos a lo importante: Swift quiere que tu código sea realmente seguro, y utilizando un valor nil es una mala idea. Tu código podría fallar, podría incluso estropear la lógica de tu código o, también, hacer que tu interfaz de usuario muestre algo incorrecto. Como resultado, cuando se declara una variable como opcional, Swift se va a asegurar de que lo gestiones forma segura.

Intentemos esto ahora: agrega las siguientes líneas de código a tu playground:

var status: String
status = getHaterStatus(weather: "rainy")

La primera linea crea una variable tipo cadena de caracteres, la segunda linea le asigna el resultado obtenido al llamar getHaterStatus() - y hoy el clima es lluvioso, por tanto los haters seguramente van a criticar.

Este código no se va a ejecutar, porque hemos dicho que status es de tipo String, que requiere un valor, sin embargo, getHaterStatus() podría no devolver uno porque devuelve una cadena de caracteres opcional. Esto es, debe, definitivamente, haber una cadena de caracteres en status, pero getHaterStatus() podría devolver nada. Swift sencillamente no te permitirá cometer este error, que es realmente útil porque efectivamente evita de lleno crear una clase con fallos (bugs).

Para solucionar este problema, necesitamos que status sea String? o eliminar la especificación de tipo completamente y dejar que Swift determine el tipo por inferencia. La primera opción es así:

var status: String?
status = getHaterStatus(weather: "rainy")

La segunda opción así:

var status = getHaterStatus(weather: "rainy")

Sin importar cual elijas, ese valor puede estar o no, y por defecto Swift no te permitirá usarlo peligrosamente. Como ejemplo, imagina una función como esta:

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

Esta acepta una cadena de caracteres y muestra un mensaje dependiendo de su contenido. Esta función recibe un valor de tipo String y no String? - no puedes pasarle un opcional aquí, recibe una cadena de caracteres como tal, lo que quiere decir que no podemos llamar esta función utilizando la variable status.

Swift ofrece dos soluciones. Ambas son válidas, sin embargo, una es preferible a la otra. La primera solución es conocida como desempaquetar el opcional (optional unwrapping) y esta se hace utilizando una sintaxis especial dentro de una sentencia condicional. Hace dos cosas al mismo tiempo: verifica si el opcional tiene un valor, si lo tiene, lo desempaqueta en un tipo de dato no opcional y finalmente ejecuta el código que le sigue.

La forma de escribirlo es así:

if let unwrappedStatus = status {
    // unwrappedStatus contains a non-optional value!
} else {
    // in case you want an else block, here you go…
}

La sentencia if let verifica y desempaqueta en una sola, simple y muy frecuentemente utilizada línea de código. Usando este método, podemos, de forma segura, desempaquetar y devolver el valor de getHaterStatus() asegurando a su vez que llamamos a la función takeHaterAction() con una cadena de caracteres no opcional válida. Debajo el código completo:

func getHaterStatus(weather: String) -> String? {
    if weather == "sunny" {
        return nil
    } else {
        return "Hate"
    }
}

func takeHaterAction(status: String) {
    if status == "Hate" {
        print("Hating")
    }
}

if let haterStatus = getHaterStatus(weather: "rainy") {
    takeHaterAction(status: haterStatus)
}

Si comprendes este concepto, ve directamente al apartado que tiene el titulo “Forzar el desempaquetado de opcionales”. Si no estás del todo seguro de comprender opcionales, continua leyendo.

Ok, si sigues por aquí, quiere decir que la explicación anterior sobre los opcionales no tuvo sentido o la entendiste, sin embargo, podrías querer aclararlo un poco mas. Los Opcionales se utilizan extensivamente en Swift, entonces es mejor que lo comprendas bien. Voy a explicarlo de nuevo de otra manera y, espero, que sea útil y te ayude!.

Tenemos la siguiente función:

func yearAlbumReleased(name: String) -> Int {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return 0
}

Esta función recibe el nombre de un disco de Taylor Swift y devuelve el año en el que ha sido publicado. Si llamamos a la función y le pasamos el nombre “Lantern” porque nos hemos confundido entre Taylor Swift y Hudson Mohawke (un error fácil de cometer, ¿verdad?) la función devuelve 0 porque no es uno de los discos de Taylor Swift.

Pero, ¿tiene 0 algún sentido?, seguramente sí, si el disco fue publicado en el 0 A.C cuando Cesar Augusto era el Emperador de Roma, 0 tiene mucho sentido, pero en nuestro caso es totalmente confuso - las personas necesitan saber de antemano qué 0 significa “no reconocido”.

Una mejor idea es reescribir esta función de forma que devuelve un entero (cuando el año ha sido encontrado) o nil (cuando no lo ha sido), que es muy sencillo gracias a los opcionales. La función resultante es esta:

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

Ahora que devuelve nil, necesitamos desempaquetar el resultado usando if let porque debemos verificar si el valor existe o no.

Si comprendes este concepto, ve directamente al apartado que tiene el titulo “Forzar el desempaquetado de opcionales”. Si no estás del todo seguro, continua leyendo.

Ok, si sigues por aquí, quiere decir que realmente está siendo complicado entender los opcionales, así que voy a explicarlo una última vez.

Tenemos aquí un arreglo de nombres:

var items = ["James", "John", "Sally"]

Si queremos escribir una función que busca en el arreglo y nos dice el índice (posición) de un nombre en particular, podríamos escribir la siguiente función:

func position(of string: String, in array: [String]) -> Int {
    for i in 0 ..< array.count {
        if array[i] == string {
            return i
        }
    }

    return 0
}

La función es, básicamente, un bucle que recorre el arreglo, devolviendo el índice del valor si lo encuentra, si no, devuelve 0.

Ahora ejecuta las siguientes líneas de código:

let jamesPosition = position(of: "James", in: items)
let johnPosition = position(of: "John", in: items)
let sallyPosition = position(of: "Sally", in: items)
let bobPosition = position(of: "Bob", in: items)

La salida sera 0, 1, 2, 0 - los indices de James y Bob son el mismo, incluso uno de ellos existiendo y el otro no. Esto es porque defino que 0 quiere decir “no encontrado”. La forma fácil de arreglarlo es hacer que -1 indique “no encontrado”, sin embargo, si es 0 o -1 aun tienes un problema para recordar, más adelante, que ese número específico significa “no encontrado”.

La solución, los opcionales: devuelve un entero si ha encontrado el valor o nil en caso contrario. De hecho, esta es la forma en la que esta implementada la función predefinida someArray.index(of: someValue) de Swift.

Cuando trabajamos con valores “podría estar, podría no”, Swift te obliga a desempaquetarlos antes de utilizarlos, por tanto, reconociendo que puede o no haber un valor. Para esto justamente se utiliza if let: si el opcional tiene un valor entonces desempaquetalo y utilízalo, si no, no lo utilices en absoluto. No es posible utilizar un valor “quizá-vacío” por accidente, porque Swift no te lo va a permitir.

Si todavía no estás seguro de cómo funcionan los opcionales, entonces lo mejor que puedes hacer es preguntarme en Twitter: me puedes encontrar en @twostraws.

Forzar el desempaquetado de opcionales

Swift te permite sobreescribir la forma segura de trabajar con los opcionales utilizando un signo de exclamación !. Si sabes que un opcional definitivamente tiene un valor, puedes forzar su desempaquetado colocando un signo de exclamación luego de este.

Por favor ten mucho cuidado: Si haces esto sobre una variable que no tiene un valor, tu código va a fallar.

Utilicemos un ejemplo funcional, esta es la base:

func yearAlbumReleased(name: String) -> Int? {
    if name == "Taylor Swift" { return 2006 }
    if name == "Fearless" { return 2008 }
    if name == "Speak Now" { return 2010 }
    if name == "Red" { return 2012 }
    if name == "1989" { return 2014 }

    return nil
}

var year = yearAlbumReleased(name: "Red")

if year == nil {
    print("There was an error")
} else {
    print("It was released in \(year)")
}

Esta función recibe el nombre de un disco. Si el disco no se puede encontrar, year será nil, y un mensaje de error se mostrará, en caso contrario, el año de publicación del disco se mostrará.

¿Cierto no?, bien, yearAlbumReleased() devuelve un entero opcional, y este código no utiliza if let para desempaquetar el opcional. Como resultado, va a mostrar “It was released in Optional(2012)” - que, probablemente, no es lo que queremos!.

Hasta este punto, ya hemos verificado que tenemos un valor válido, por tanto no tiene mucho sentido incluir otro if let para desempaquetar el opcional de forma segura. Swift ofrece una solución - cambia la segunda sentencia print() por lo siguiente:

print("It was released in \(year!)")

Fíjate en el signo de exclamación: quiere decir “Estoy seguro que esto contiene un valor, fuerza el desempaquetado.”

Opcionales desempaquetados implícitamente

También puedes utilizar el signo de exclamación para crear opcionales desempaquetados implícitamente, que es cuando las personas, en general, comienzan a confundirse. Por tanto, por favor, lee lo siguiente con calma y cuidado.

  • Una variable normal (regular) debe contener un valor. Por ejemplo: String debe contener una cadena de caracteres, incluso si esta está vacía. De modo que "" no puede ser nil.
  • Una variable opcional puede contener un valor, o no. Debe ser desempaquetada antes de utilizarla. Por ejemplo: String? podría contener una cadena de caracteres o podría contener nil. La única forma de saberlo es desempaquetando.
  • Un opcional desempaquetado implícitamente puede contener un valor, o no. Pero este no necesita ser desempaquetado antes de utilizarlo. Swift no lo va a verificar por ti, por tanto necesitas ser muy cuidadoso. Por ejemplo: String! puede contener una cadena de caracteres o puede contener nil - y es responsabilidad tuya utilizarlo apropiadamente. Es como un opcional normal, pero Swift te permite acceder a su valor directamente sin desempaquetarlo de forma segura. Si intentas hacerlo, esto quiere decir que estas seguro de que contiene un valor - pero si te equivocas tu aplicación fallará.

La mayoría de las veces en las que verás opcionales desempaquetados implícitamente es cuando trabajas con elementos de la interfaz de usuario de UIKit en iOS o AppKit en macOS. Estos necesitan se declarados de antemano pero no puedes utilizarlos hasta que han sido creados - y a Apple le gusta crear los elementos de la interfaz de usuario en el ultimo momento posible para evitar trabajo innecesario. Teniendo que desempaquetar valores constantemente definitivamente sabrás que es tedioso por eso se hace desempaquetado implícito.

No te preocupes si consideras los opcionales desempaquetados implícitamente un poco complejo de entender - se volverá mucho mas comprensible a medida que trabajas con el lenguaje.

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.