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
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
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
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
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 queDouble
.
let patas: Int = 4
let precio: Double = 30.50
let temperatura: Float = 20.2
Bool
Bool
es un valor booleano. Es true
o false
. Solo admite esas dos posibilidades.
let encendido: Bool = true
let alumnoPresente: Bool = false
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"
Un dato de color sobre los String
en Swift es que permiten emojis!
Por qué? Por qué no? 🤷
let fancyHelloWorld: String = "👋 🌎!"
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!")
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
Comentarios multilínea
Comienzan con /*
, y terminan con */
.
/* Esta línea
imprime el
mensaje de hola mundo */
print(mensaje)
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
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."
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"]
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]
Hay varias maneras de definir un Array
vacío.
let numerosVacio1: [Int] = [] // Aconsejable
let numerosVacio2: Array<Int> = []
let numerosVacio3 = [Int]()
let numerosVacio4 = Array<Int>()
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")
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.")
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"
Si queremos saber si un Array
está vacío, usaremos la propiedad isEmpty
, que devuelve un Bool
.
if numerosVacio1.isEmpty {
print("Está vacío!")
}
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
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]
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
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"
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"
]
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
for nombre in nombres {
print("Nombre: \(nombre)")
}
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
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)")
}
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
for numero in 0 ... 10 {
print("Número actual: \(numero)")
}
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])")
}
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
var index = 0
while index < nombres.count {
print("Nombre con while: \(nombres[index])")
index += 1
}
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)!")
}
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")
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")
¡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
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.
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
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
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
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]
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 unString
que puede tener valor o bien sernil
(nulo en Swift). -
Int?
es unInt
que puede sernil
. -
Bool?
es unBool
que puede sernil
. -
[String]?
es unArray
deString
que puede sernil
. -
(cociente: Int, resto: Int)?
es una tupla de dosInt
. La tupla puede sernil
. Los componentes de la tupla, en caso de existir ésta, no pueden ser nulos. -
(primerNombre: String, segundoNombre: String?)
es una tupla de dosString
. 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
)
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
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")
}
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
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!
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
)
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.
Ejercicios Primera Parte
Resolver los siguientes ejercicios:
- 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 camposnombreDeCalle
,numero
,entrecalle1
yentrecalle2
. - Dentro de la dirección, definir algunos tipos de datos opcionales, entre ellos
piso
ydepartamento
. - Definir tres direcciones en constantes.
- Escribir una función que reciba una dirección y la imprima como un
String
bien formateado. Hacer uso de la interpolación. - Escribir una función que reciba un
Array
de direcciones y devuelva unString
que contenga"piso: \(piso) ; depto: \(departamento)"
, SOLO para las direcciones que tengan definidos tanto un piso como un departamento.
Top comments (0)