DEV Community

Fernando Martín Ortiz
Fernando Martín Ortiz

Posted on • Edited on

Sobrevivir en Swift: Parte 1

Esta es la parte 1 de un post en dos partes.
La parte 2 aquí

Características de Swift

Swift es un lenguaje de programación desarrollado por Apple para ser utilizado en el desarrollo de aplicaciones para sus plataformas, en reemplazo de Objective-C, su anterior lenguaje de preferencia. De este modo, y usando Xcode, se puede desarrollar aplicaciones para iOS, iPad OS, MacOS, WatchOS y TVOS. A partir del año 2015, Swift es también Open Source, mantenido y desarrollado no solo por Apple, sino por una comunidad abierta de desarrolladores.

Swift es multiparadigma, lo cual significa que podemos utilizarlo para desarrollar código orientado a objetos (OOP), programación funcional, procedural, scripting, etc. Swift se adapta a nuestro estilo de preferencia, si bien la comunidad ha acordado buenas prácticas y una forma de desarrollar software siguiendo sobre todo, la orientación a objetos.

Swift es moderno, lo que significa que incorpora funcionalidades de los lenguajes de programación más modernos, como veremos en el curso. Además, sigue desarrollándose e incorporando nuevas funcionalidades.

Swift es sencillo de aprender, pero puede volverse tan complejo como necesitemos.

Durante este curso veremos la sintaxis de Swift y ejemplos de uso. Lo necesario como para que podamos comenzar a desarrollar aplicaciones de iOS, pero es importante notar que el lenguaje puede llegar a ser mucho más complejo que lo que veremos aquí. Este curso no pretende ser completo y exhaustivo en Swift, sino brindar las herramientas necesarias para iniciar.

Variables

Las variables están presentes en la mayoría de los lenguajes de programación, y son nombres (llamados identificadores), que contienen un valor.
En Swift, las variables se declaran con la palabra clave var, seguido de un = y el valor que contiene. Por ejemplo:

var nombre = "Fernando"
var calle = "Avenida Rivadavia"
var patasDeUnaSilla = 4
Enter fullscreen mode Exit fullscreen mode

Algo que puede resultar muy curioso en esas declaraciones es que no estamos especificando el tipo. Esto es porque Swift incorpora Inferencia de tipos. La inferencia de tipos nos permite escribir las variables sin ser explícitos en su tipo, porque Swift es lo suficientemente "inteligente" como para darse cuenta que un 4 es un Int, y que "Fernando" es un String.

Podemos ser explícitos

Claro, hay momentos en los que nos puede ser útil ser explícitos en el tipo de dato. Por ejemplo, si hablamos de un precio

var precio1 = 12
Enter fullscreen mode Exit fullscreen mode

Quizás queramos especificar que ser trata de un número decimal, y no de un Int. En Swift podemos especificar el tipo con : <TipoDeDato>. Por ejemplo:

var precio2: Double = 2.50
Enter fullscreen mode Exit fullscreen mode

Constantes

Supongamos que tengamos una variable con un valor que sabemos que no cambiará. Por ejemplo, un gato tiene cuatro patas. Si queremos definir la cantidad de patas de un gato, podremos definir una constante. Para hacerlo, cambiaremos var por let, convirtiendo esa variable en una constante.
Una constante es una variable a la que solo se podrá asignar un valor una vez. Si queremos modificar su valor luego de esto, el mismo compilador nos arrojará un error.

var hojasDeUnArbol = 10
hojasDeUnArbol = 22
hojasDeUnArbol = 25 // No recibiremos errores al querer modificar una variable declarada con var.
let patasDeUnGato = 4
// Descomenten la siguiente línea para verificar que realmente esto dará error.
// patasDeUnGato = 5
Enter fullscreen mode Exit fullscreen mode

Al nombrar una variable, es importante ser muy explícitos en su significado. Esto quiere decir, no abreviemos.

nom no es un nombre de variable aconsejable. nombre sí lo es.
cantDigitos no es un nombre de variable aconsejable. cantidadDeDigitos sí lo es.
i no es un nombre de variable aconsejable. index sí lo es.

Si bien en el curso usaremos nombres en español, en la práctica todos serán en inglés.

Cada lenguaje de programación tiene su forma "correcta" de escribirse, lo que podemos considerar Buenas prácticas. En Swift, la buena práctica es definir las variables como constantes, usando let, y dejar var solo para los casos en los que sea estrictamente necesario. Esto nos puede ahorrar dolores de cabeza, porque sabemos que la variable que estamos utilizando, si fue definida con let, no ha sufrido modfiicaciones. Para funciones complejas, esto puede ser muy importante.

Tipos de datos

Como vimos en la sección anterior, si bien no es necesario que seamos explícitos en el tipo de dato de una variable, porque el compilador infiere el tipo de dato, podemos elegir ser explícitos y definirlo de todos modos. En esta sección elegiremos ese camino.
Swift viene con un conjunto de tipos de dato predefinidos, y nos permite definir los nuestros, como veremos en las siguientes secciones.

Tipos de datos numéricos

Swift incorpora los siguientes tipos de datos numéricos principales:

  • Int: Número entero.
  • Double: Número decimal.
  • Float: Número decimal, pero de menor capacidad que Double.
let patas: Int = 4
let precio: Double = 30.50
let temperatura: Float = 20.2
Enter fullscreen mode Exit fullscreen mode

Bool

Bool es un valor booleano. Es true o false. Solo admite esas dos posibilidades.

let encendido: Bool = true
let alumnoPresente: Bool = false
Enter fullscreen mode Exit fullscreen mode

String

String es una cadena de caracteres. Se utiliza para definir textos y palabras. Por ejemplo, un nombre o una calle podrían definirse como String.

let nombre: String = "Fernando"
Enter fullscreen mode Exit fullscreen mode

Un dato de color sobre los String en Swift es que permiten emojis!
Por qué? Por qué no? 🤷

let fancyHelloWorld: String = "👋 🌎!"
Enter fullscreen mode Exit fullscreen mode

Print

A veces necesitamos imprimir por consola algún valor. En Swift, esto lo hacemos con la función print(). Por ejemplo, un "Hola, Mundo!", el programa típico para aprender a programar, se puede escribir de esta manera:

print("Hola, Mundo!")
Enter fullscreen mode Exit fullscreen mode

Si se ejecuta el programa, con el botón de la barra izquierda, se mostrará ese mensaje en la consola de la parte inferior de la pantalla.

Comentarios

Los comentarios pueden ser de una línea, o multilínea.

Comentarios de una línea

Se escriben con //, y solo abarcan una línea.

let mensaje = "Hello, world!" // Este es un mensaje
Enter fullscreen mode Exit fullscreen mode

Comentarios multilínea

Comienzan con /*, y terminan con */.

/* Esta línea
 imprime el
 mensaje de hola mundo */
print(mensaje)
Enter fullscreen mode Exit fullscreen mode

Interpolation

¿Cómo formamos String a partir de otras variables? Una forma sencilla es sumando.

let nombre = "Fernando"
let apellido = "Ortiz"
let nombreCompleto = nombre + apellido // Fernando Ortiz
Enter fullscreen mode Exit fullscreen mode

La alternativa, y lo más utilizado en Swift, es la interpolación. Una interpolación de String consiste en "incrustar" variables dentro de un String. Esto se hace introduciendo \(<variable>) dentro del String. Por ejemplo:

let segundoNombre = "Martín"
let experiencia = 6
let descripcion = "Mi nombre es \(nombre) \(segundoNombre) \(apellido) y tengo \(experiencia) años de experiencia en desarrollo de iOS."
print(descripcion) // "Mi nombre es Fernando Martín Ortiz y tengo 6 años de experiencia en desarrollo de iOS."
Enter fullscreen mode Exit fullscreen mode

Noten también que estamos introduciendo un Int en el medio del String y no nos ha dado problemas. Esta es otra ventaja de la interpolación.

Contenedores

Llamaremos Contenedores en Swift a los tipos de datos que contienen otros tipos de datos en su interior y los podemos recorrer de alguna manera.

Array

También llamados arreglos, listas o vectores, son tipos de datos que almacenan valores en forma secuencial. La particularidad que tienen en Swift es que no se pueden mezclar valores con diferentes tipos dentro de un mismo Array.
La forma más simple de definir un Array es esta:

let alumnos = ["Federico", "Micaela", "Aldana", "Oscar"]
Enter fullscreen mode Exit fullscreen mode

Nótese que solo hemos utilizado valores de String. Un array con valores ["Fernando", 28, true] no sería válido, a menos que el array sea de tipo [Any]. El tipo Any es un comodín que cuenta por todos los tipos, pero nos da ciertas desventajas de las cuales no hablaremos en este momento. Mi consejo aquí es NO LO HAGAN.

Si queremos definir el tipo y ser explícitos, podemos hacerlo:

let numeros1: [Int] = [1, 21, 129]
let numeros2: Array<Int> = [3, 122]
Enter fullscreen mode Exit fullscreen mode

Hay varias maneras de definir un Array vacío.

let numerosVacio1: [Int] = [] // Aconsejable
let numerosVacio2: Array<Int> = []
let numerosVacio3 = [Int]()
let numerosVacio4 = Array<Int>()
Enter fullscreen mode Exit fullscreen mode

Métodos útiles en Array

Si queremos contar la cantidad de elementos en un Array, usaremos la propiedad count.

let numeros3 = [12, 31, 231, 23]
print("\(numeros3) tiene \(numeros3.count) elementos")
Enter fullscreen mode Exit fullscreen mode

Dato curioso, los String en Swift son en realidad secuencias de caracteres, y también tienen la propiedad count. De hecho, la mayoría de lo que explicaremos aquí, aplica para String.

let nombre = "Fernando"
let cantidadDeCaracteres = nombre.count
print("\(nombre) tiene \(cantidadDeCaracteres) letras.")
Enter fullscreen mode Exit fullscreen mode

Si queremos obtener el primer elemento de un Array, usaremos .first!, la razón del ! lo explicaremos más adelante. Solo digamos por el momento, que usaremos ! para afirmar que sabemos con total seguridad que hay un primer elemento en el Array, que no está vacío. Noten que si hacemos .first! en un Array vacío, el programa se detendrá y lanzará un error.

let nombres = ["Tamara", "Nicolás", "Francisco"]
let primerNombre = nombres.first! // "Tamara"
Enter fullscreen mode Exit fullscreen mode

Si queremos saber si un Array está vacío, usaremos la propiedad isEmpty, que devuelve un Bool.

if numerosVacio1.isEmpty {
    print("Está vacío!")
}
Enter fullscreen mode Exit fullscreen mode

Si queremos agregar un elemento a un Array, usaremos la función append

var nombresALlenar: [String] = []
nombresALlenar.append("Fernando")
nombresALlenar.append("Martín")
print("Nombres a llenar ahora tiene \(nombresALlenar.count) elementos") // Nombres a llenar ahora tiene 2 elementos
Enter fullscreen mode Exit fullscreen mode

Si queremos acceder a una posición exacta de un Array, usamos lo que se conoce como subscript, y es básicamente un número (el índice) entre corchetes. Los índices en un Array de Swift comienzan desde 0. Noten que si queremos obtener un elemento en una posición que no está definida, el programa se detendrá y lanzará un error.

let nombres2 = ["Aldana", "Iván", "Marcos", "Cristian"]
let tercerNombre = nombres2[2] // "Marcos"
// Esto fallará al ejecutarse.
// let quintoNombre = nombres2[4]
Enter fullscreen mode Exit fullscreen mode

Diccionarios

Un Dictionary en Swift es un contenedor que asocia nombres a valores. También se llaman key-value, o clave-valor. El diccionario estará compuesto por pares clave-valor, donde la clave es por lo general un String, y el valor puede ser cualquier cosa. Aquí no rige tan fuertemente la restricción de que los valores sean todos del mismo tipo, y el tipo Any es más utilizado.
Los Dictionary son análogos al tipo Map en otros lenguajes de programación.

let persona: [String: Any] = [ // Noten que al definirlo con `Any`, el valor puede ser de cualquier tipo.
    "nombre": "Ricardo",
    "apellido": "Gomez",
    "edad": 27,
    "cargo": "Project Manager"
]

let familia: [String: String] = [ // Al definirlo como [String:String], el valor solo puede ser String
    "madre": "Marge",
    "padre": "Homero",
    "hijo": "Bart",
    "hija": "Lisa",
    "bebé": "Maggie",
    "perro": "Huesos" // no es Ayudante de Santa, lo lamento. 😁
]

let diccionarioVacio: [String: Any] = [:] // El `:` debe estar, para que no se confunda con un Array vacío
Enter fullscreen mode Exit fullscreen mode

La ventaja de poder definir clave-valor es que podemos acceder a cualquier valor utilizando su clave. Siguiendo el ejemplo de la familia:

let madre = familia["madre"]! // "Marge"
Enter fullscreen mode Exit fullscreen mode

El ! es necesario porque debemos asegurarle al compilador que realmente existe un valor para esa clave

For

El for es una estructura muy conocida en los lenguajes de programación en general. En Swift es algo diferente. Un for nos permite ejecutar una porción de código un número predefinido de veces. En la práctica, el caso más común es recorrer un contenedor, como un Array o un Dictionary.

// Usaremos estos valores para las demostraciones
let nombres = ["Ayelén", "Lautaro", "Natalia", "Sergio", "Gerardo"]
let persona: [String: Any] = [
    "nombre": "Ricardo",
    "apellido": "Gomez",
    "edad": 27,
    "cargo": "Project Manager"
]
Enter fullscreen mode Exit fullscreen mode

for-in - Array

Para recorrer un Array, podemos recorrerlo elemento por elemento usando for-in.
En este caso imprimirá:

 Nombre: Ayelén
 Nombre: Lautaro
 Nombre: Natalia
 Nombre: Sergio
 Nombre: Gerardo
Enter fullscreen mode Exit fullscreen mode
for nombre in nombres {
    print("Nombre: \(nombre)")
}
Enter fullscreen mode Exit fullscreen mode

for-in - Dictionary

También podremos recorrer diccionarios con for-in. En este caso la sintaxis es algo diferente, porque por cada elemento del Dictionary obtendremos un par (tuple realmente, como veremos en una sección posterior), con la clave y el valor correspondiente. Veamos un ejemplo. Imprimirá:

 apellido : Gomez
 cargo : Project Manager
 nombre : Ricardo
 edad : 27
Enter fullscreen mode Exit fullscreen mode

Noten también que un Dictionary no preserva el orden de los campos. En este ejemplo imprimió nombre en tercer lugar, sin importar que lo haya definido en primer lugar.

for (atributo, valor) in persona {
    print("\(atributo) : \(valor)")
}
Enter fullscreen mode Exit fullscreen mode

Ranges

Una forma alternativa de utilizar los for es definiendo un Range o rango. Un Range define un intervalo de valores.

  • (0 ...< 10) por ejemplo, define un rango desde 0 a 10, sin incluir el 10.
  • (0 ... 10) define un rango desde 0 a 10 inclusive.

Imprimirá

 Número actual: 0
 Número actual: 1
 Número actual: 2
 Número actual: 3
 Número actual: 4
 Número actual: 5
 Número actual: 6
 Número actual: 7
 Número actual: 8
 Número actual: 9
 Número actual: 10
Enter fullscreen mode Exit fullscreen mode
for numero in 0 ... 10 {
    print("Número actual: \(numero)")
}
Enter fullscreen mode Exit fullscreen mode

Podemos utilizar la propiedad count de Array para recorrerlo de una manera más tradicional. Noten que for index in 0 ..< nombres.count es muy similar a algo como for var index = 0 ; index < nombres.count ; index++, sintaxis que no existe en Swift. Y si se preguntan, no, no existe index++ en Swift. Lo que sí existe es index += 1, pero de todos modos la forma tradicional de for no está presente en Swift.

for index in 0 ..< nombres.count {
    print("Nombre actual: \(nombres[index])")
}
Enter fullscreen mode Exit fullscreen mode

While

La cláusula while es muy utilizada en lenguajes de programación. Sin embargo, si debo ser honesto, en mis seis años desarrollando aplicaciones de iOS, no recuerdo haber tenido que utilizarla alguna vez. La mayoría de las veces, una variación de for nos permite el mismo resultado, y es preferible por lo general, porque al utilizar while siempre tenemos la posibilidad de caer en un ciclo infinito de modo accidental.
En este apartado, de todos modos mostraré cómo utilizar while, pero repito, practicamente no es necesario utilizarlo.
while es una sentencia que nos permite definir una condición y un bloque de código. La condición se evaluará. Si es true, entonces se ejecutará el bloque de código. Este proceso se repetirá indefinidamente hasta que la condición se evalúe como false.

Este ejemplo imprimirá:

 Nombre con while: Ayelén
 Nombre con while: Lautaro
 Nombre con while: Natalia
 Nombre con while: Sergio
 Nombre con while: Gerardo
Enter fullscreen mode Exit fullscreen mode
var index = 0
while index < nombres.count {
    print("Nombre con while: \(nombres[index])")
    index += 1
}
Enter fullscreen mode Exit fullscreen mode

Funciones

Las funciones son bloques de código que reciben opcionalmente un conjunto de datos de entrada, llamado input (o parámetros, o argumentos), y devuelve, opcionalmente, un valor como resultado, llamado output.
En Swift, las funciones se declaran con la palabra clave func. La estructura para declarar una función es la siguiente:

func <nombreDeLaFuncion> (<input1>: <TipoInput1>, <input2>: <TipoInput2>, ..., <inputN>: <TipoInputN>) -> <TipoOutput> { <cuerpo> }

Las funciones pueden o no tener un Output, como se ha dicho anteriormente. Sin embargo, si lo declaran, deben devolverlo utilizando la palabra clave return.

Todo esto puede resultar confuso pero es en realidad muy intuitivo, veamos algunos ejemplos:

func sumar(x: Int, y: Int) -> Int {
    return x + y
}

func holaMundo() {
    print("Hola, Mundo!")
}

func saludar(nombre: String) {
    print("Hola, \(nombre)!")
}
Enter fullscreen mode Exit fullscreen mode

La función sumar recibe dos parámetros, x e y, y devuelve la suma de ambos (tiene Output).
La función holaMundo, no tiene input, y tampoco Output. Solo imprime "Hola, Mundo!" por consola.
La función saludar recibe un input nombre, y no devuelve nada.

Invocación

Las funciones pueden ser llamadas (o "invocadas") por su nombre y el nombre de sus argumentos en forma explícita. Esto es muy importante y es diferente a lo que sucede con otros lenguajes de programación.

let resultadoSumar = sumar(x: 10, y: 14) // 24
holaMundo()
saludar(nombre: "Fernando")
Enter fullscreen mode Exit fullscreen mode

Legibilidad

Ahora bien, si son detallistas, habrán notado que saludar(nombre: "Fernando") no se lee muy bien que digamos. Podemos corregir esto. ¿No les resultaría más sencillo leer saludar(a: "Fernando")? Sin embargo, esto nos pondría en un problema aún peor, porque dentro de la función tendríamos una variable a, con el valor "Fernando".
En realidad no estamos obligados a elegir una forma u otra. Swift distingue nombres internos y nombres externos para los parámetros de las funciones. Hagamos una modificación, con una función nueva decirHola.

func decirHola(a nombre: String) {
    print("Hola, \(nombre)!")
}

decirHola(a: "Fernando")
Enter fullscreen mode Exit fullscreen mode

¡Mucho mejor! Esta función decirHola recibe un solo argumento, con un nombre interno nombre, visible dentro del cuerpo de la función y un nombre externo a, visible desde el punto de invocación de la función (cuando la llamamos). Si no especificamos nombres diferentes para el nombre interno y el nombre externo del argumento, ambos serán el mismo, como en los primeros ejemplos.

Resolvamos otro problema. La función sumar se ve rara. En vez de sumar(x: 10, y: 14), sería más natural decir sumar(10, 14) o sumar(10, a: 14), o similar. Swift nos permite definir nombres externos vacíos con el nombre reservado _ (guión bajo). El guión bajo nos permite especificar que no queremos que tenga un nombre externo. Intentemos realizar una multiplicación para que se vea como multiplicar(2, por: 8)

func multiplicar(_ x: Int, por y: Int) -> Int {
    return x * y
}
multiplicar(2, por: 8) // 16
Enter fullscreen mode Exit fullscreen mode

Precondiciones

Una precondición es una condición que debe complirse para que una función sea ejecutada. Si la precondición no se cumple, entonces la función devuelve un valor adecuado o informa del error. En Swift expresamos las precondiciones con la cláusula guard. Podemos leerlo como "Asegurate de que esto se cumpla. Si no, devolvé este valor."

func describirDivision(de dividendo: Int, por divisor: Int) -> String {
    guard divisor != 0 else {
        return "No es posible dividir por cero."
    }
    let resultado = dividendo / divisor
    return "El resultado de dividir \(dividendo) por \(divisor) es \(resultado)."
}

let descripcion1 = describirDivision(de: 20, por: 5)
print(descripcion1) // El resultado de dividir 20 por 5 es 4.

let descripcion2 = describirDivision(de: 10, por: 0)
print(descripcion2) // No es posible dividir por cero.
Enter fullscreen mode Exit fullscreen mode

Buenas prácticas

Hasta aquí la teoría sobre la sintaxis de las funciones. El fragmento que sigue es completamente opcional e incluso pueden leerlo al finalizar este curso, pero me parece importante destacarlo.
¿Por qué escribimos funciones? Hay varias razones, pero lo importante es entender que al función es la herramienta fundamental que tenemos a disposición para escribir abstracciones en nuestro código. Una abstracción es en cierta forma adaptar un concepto a términos que nos permitan razonar sobre él en forma más intuitiva. Considerando que escribimos código para expresar una solución a un problema de forma que no solo una computadora lo entienda (que es la parte más sencilla), sino para que otras personas lo lean, lo entiendan y puedan sentirse seguros modificándolo y extendiéndolo, entonces las abstracciones nos permiten sumar claridad a nuestro código.
Una buena función entonces es entendible, clara, legible y modificable. Voy a enumerar a continuación buenas prácticas al momento de escribir una función.

Funciones cortas

Una función no debería ocupar más de diez o quince líneas de código por lo general. Si una función se extiende más que esa cantidad de líneas, es conveniente considerar separarla en funciones más pequeñas y componerlas. Una función de una sola línea no es algo malo si suma claridad.

Funciones legibles

Esto aplica tanto para el cuerpo de la función como para la invocación de la misma. El código que va dentro de una función tiene que ser una secuencia de pasos bien definida. La invocación de una función debe leerse naturalmente.

Funciones cohesivas

Una función debe hacer una sola cosa y hacerla bien. Esto es incluso más prioritario que escribir funciones cortas. Si una función tiene 25 líneas de código pero todas contribuyen al objetivo sin sumar carga cognitiva a la persona que lo está leyendo

Tuplas

Una tupla es el primer tipo de dato personalizado que veremos en el curso. Las tuplas son conjuntos de datos bajo un nombre común, y sin ninguna funcionalidad adicional (como sí dan las clases, por ejemplo). Una tupla se puede definir con la palabra clave typealias, o bien al momento de generarse.

typealias NombreCompleto = (primerNombre: String, apellido: String)
let nombre: NombreCompleto = (primerNombre: "Fernando", apellido: "Ortiz")
print("El nombre es \(nombre.primerNombre) y el apellido es \(nombre.apellido)") // El nombre es Fernando y el apellido es Ortiz
Enter fullscreen mode Exit fullscreen mode

El caso más común para utilizar una tupla es para permitir que una función devuelva más de un valor.

func dividir(_ dividendo: Int, por divisor: Int) -> (cociente: Int, resto: Int) {
    // Asumimos que no estamos dividiendo por cero
    return (
        cociente: Int(dividendo / divisor), // Le quitamos lo que va después de la coma al convertirlo a entero
        resto: dividendo % divisor // El operador módulo nos devuelve el resto de la división
    )
}

let resultado = dividir(13, por: 4)
print("Cociente: \(resultado.cociente) ;; Resto: \(resultado.resto)") // Cociente: 3 ;; Resto: 1
Enter fullscreen mode Exit fullscreen mode

Destructuring

Destructuring es una forma de separar una tupla en sus componentes. Se realiza igualando una tupla con un conjunto de variables entre paréntesis y separados por coma:

let (miCociente, miResto) = dividir(17, por: 3)
print("Cociente: \(miCociente) ;; Resto: \(miResto)") // Cociente: 5 ;; Resto: 2

// También podemos ignorar alguno de esos valores con un guión bajo
let (cociente, _) = dividir(50, por: 4) // Ignoramos el resto aquí
print("El resultado de dividir 50 por 4 es \(cociente)") // El resultado de dividir 50 por 4 es 12
Enter fullscreen mode Exit fullscreen mode

Componentes anónimos

Una tupla puede tener sus componentes anónimos. Si bien no es recomendable, es posible. En este caso podemos obtener sus componentes según el orden de los mismos, usando tupla.0 para el primer elemento, tupla.1 para el segundo, etc. O bien aplicando destructuring let (primero, segundo) = tupla lo cual es preferible.
Veamos un último ejemplo generando intervalos usando una función que devolverá el valor menor y el mayor en una tupla.

func generarIntervalo(valor: Int, amplitud: Int) -> (Int, Int) {
    let cotaInferior = valor - amplitud
    let cotaSuperior = valor + amplitud
    return (cotaInferior, cotaSuperior)
}

// Con destructuring - Preferible.
let (inferior, superior) = generarIntervalo(valor: 10, amplitud: 2)
print("[\(inferior);\(superior)]") // [8;12]

// Accediendo por orden a los componentes anónimos
let intervalo = generarIntervalo(valor: 10, amplitud: 2)
print("[\(intervalo.0);\(intervalo.1)]") // [8;12]
Enter fullscreen mode Exit fullscreen mode

Tipos de datos opcionales

Imaginemos que queremos describir una persona. ¿Qué atributos tiene una persona? Podemos decir nombre, apellido, dni, edad, etc. Todos ellos existen. Una persona no puede no tener nombre. Una persona no puede no tener apellido. Estos seguramente son de tipo String. Lo que se conoce como un tipo no opcional.
Sin embargo, esto no aplica para todos los atributos de una persona. ¿Qué sucede con el segundoNombre? Una persona puede no tener segundoNombre, es OPCIONAL.
Los tipos de datos opcionales en Swift permiten modelar justamente eso, valores que pueden estar ausentes, ser nulos. Posiblemente habrán notado que hasta el momento, en todos los ejemplos se usaron valores no opcionales, y que solo en pequeñas partes se usaron los ! para aclarar que un valor "estábamos seguros de que existía", como en first! para los Array.
Todos los tipos de datos tienen su contraparte opcional, que se define con el sufijo ?. Así por ejemplo

  • String? es un String que puede tener valor o bien ser nil (nulo en Swift).
  • Int? es un Int que puede ser nil.
  • Bool? es un Bool que puede ser nil.
  • [String]? es un Array de String que puede ser nil.
  • (cociente: Int, resto: Int)? es una tupla de dos Int. La tupla puede ser nil. Los componentes de la tupla, en caso de existir ésta, no pueden ser nulos.
  • (primerNombre: String, segundoNombre: String?) es una tupla de dos String. El primero no puede ser nulo. El segundo sí puede serlo. La tupla no puede ser nula.
typealias DatosDePersona = (nombre: String, segundoNombre: String?, apellido: String, edad: Int)
let fernando: DatosDePersona = (
    nombre: "Fernando",
    segundoNombre: "Martín",
    apellido: "Ortiz",
    edad: 29
)
let nicolas: DatosDePersona = (
    nombre: "Nicolás",
    segundoNombre: nil, // Es nulo, no existe.
    apellido: "Duarte",
    edad: 29
)
Enter fullscreen mode Exit fullscreen mode

Forzar un opcional

Si sabemos que un valor opcional realmente tiene valor, podemos forzarlo. Para ello se añade el sufijo ! al valor, y pasará de ser opcional, a ser no opcional.
A continuación algunos ejemplo. Sin embargo, en la práctica esto es una MUY MALA PRÁCTICA. Repito, MUY MALA PRÁCTICA. Si añadimos ! para obtener un valor no opcional, que en realidad era nil, la aplicación se cerrará completamente por el error.

print("El segundo nombre de Fernando es \(fernando.segundoNombre)")
// El segundo nombre de Fernando es Optional("Martín")
// Debemos obtener el valor no opcional de Fernando

print("El segundo nombre de Fernando es \(fernando.segundoNombre!)")
// El segundo nombre de Fernando es Martín
Enter fullscreen mode Exit fullscreen mode

if-let

Esta es una de las formas "correctas" de manejar opcionales. Dentro de un if se puede incluir una asignación de un opcional que solo se ejecutará si el valor no es nil. Por ejemplo:

if let segundoNombre = fernando.segundoNombre {
    // Aquí adentro, segundoNombre no es opcional
    print("El segundo nombre de Fernando es \(segundoNombre)") // El segundo nombre de Fernando es Martín
}

if let segundoNombre = nicolas.segundoNombre {
    // No se ejecutará.
    print("El segundo nombre de Nicolás es \(segundoNombre)")
} else {
    print("Nicolás no tiene segundo nombre")
}
Enter fullscreen mode Exit fullscreen mode

guard-let

Un guard es lo contrario de un if. Entra en el cuerpo de la estructura solo en caso de que la condición no se cumpla, y se usa al escribir funciones para expresar una precondición.
guard también puede utilizarse para eliminar opcionales de una manera muy similar a if-let.

func obtenerSegundoNombre(de persona: DatosDePersona) -> String {
    guard let segundoNombre = persona.segundoNombre else {
        return "\(persona.nombre) no tiene segundo nombre"
    }
    return "El segundo nombre de \(persona.nombre) es \(segundoNombre)"
}

print(obtenerSegundoNombre(de: fernando)) // El segundo nombre de Fernando es Martín
print(obtenerSegundoNombre(de: nicolas)) // Nicolás no tiene segundo nombre
Enter fullscreen mode Exit fullscreen mode

Implicitly unwrapped optionals

Perdón por el inglés repentino, pero así lo encontrarán en internet si lo buscan. Lo que venimos realizando, de "eliminar la opcionalidad" se llama en realidad unwrap. Un implicitly unwrapped optional es un tipo de dato opcional, al que podemos asignarle nil, obviamente, pero que al momento de utilizarse, se considerará no opcional pase lo que pase. Por supuesto, la aplicación se cerrará por un error en caso de que lo queramos utilizar siendo nulo. Y por supuesto es una mala práctica. Sin embargo, en IOS se utiliza mucho, sobre todo en los frameworks que están escritos en Objective-C (el predecesor de Swift en las plataformas de Apple).

let segundoNombreNoNulo: String! = "Marcos"
let segundoNombreNulo: String! = nil

func imprimirSegundoNombre(_ segundoNombre: String) {
    print(segundoNombre)
}

imprimirSegundoNombre(segundoNombreNoNulo) // Marcos
//imprimirSegundoNombre(segundoNombreNulo) // CRASH!
Enter fullscreen mode Exit fullscreen mode

Optional chaining

Imaginemos el caso en el que tengamos un objeto opcional.

var personaOpcional: DatosDePersona?
personaOpcional = (
    nombre: "Nicolás",
    segundoNombre: nil,
    apellido: "Duarte",
    edad: 29
)
Enter fullscreen mode Exit fullscreen mode

Si en este caso, en el que personaOpcional es opcional, queremos acceder a alguno de sus miembros, sean atributos o métodos, podemos utilizar el mecanismo llamado Optional chaining.

Optional chaining nos permite acceder a los miembros del objeto opcional, usando ?. en lugar de ..

let nombreDePersonaOpcional = personaOpcional?.nombre // es de tipo String?, aunque `nombre` sea String, ya que no tenemos la certeza de que personaOpcional exista.
Enter fullscreen mode Exit fullscreen mode

Ejercicios Primera Parte

Resolver los siguientes ejercicios:

  1. Definir una tupla que describa una dirección, con campos como ciudad, partido, provincia, calle, pais, codigoPostal, etc. Siéntanse libres de colocar todos los campos que consideren relevantes. Usar un diccionario para la calle con los campos nombreDeCalle, numero, entrecalle1 y entrecalle2.
  2. Dentro de la dirección, definir algunos tipos de datos opcionales, entre ellos piso y departamento.
  3. Definir tres direcciones en constantes.
  4. Escribir una función que reciba una dirección y la imprima como un String bien formateado. Hacer uso de la interpolación.
  5. Escribir una función que reciba un Array de direcciones y devuelva un String que contenga "piso: \(piso) ; depto: \(departamento)", SOLO para las direcciones que tengan definidos tanto un piso como un departamento.

Top comments (0)