Swift – Ejercicios resueltos (II)

Funciones y operadores aritméticos

Sumar dos números y devolver el resultado:

func suma(_ num1:Int, _ num2:Int) -> Int {
    return num1 + num2
}
let num1 = 5
let num2 = 8
print("\(num1) + \(num2) = \(suma(num1, num2))")

Calcular el número de segundos que suman en total un número determinado de años:

func segundosAnyo(_ anyos:Int) -> Int {
    let dias = 365
    let horas = 24
    let segundos = 3600
    return dias * horas * segundos * anyos
}
let anyos = 3
print("La cantidad de segundos en \(anyos) años es \(segundosAnyo(anyos))")

Calcular la cantidad total de píxeles que tiene una pantalla dada su altura y anchura en píxeles:

func pixeles(alto:Int, ancho:Int) -> Int {
    return(alto * ancho)
}
let alto = 720
let ancho = 480
print("La cantidad de píxeles de una pantalla de \(alto)x\(ancho) es \(pixeles(alto:720, ancho:480))")

Intercambiar el valor de dos variables:

func intercambiar(_ a:inout Int, _ b:inout Int) {
    let aux = a
    a = b
    b = aux
}
var num1 = 5
var num2 = 10
intercambiar(&num1, &num2)
print("El primer número es: \(num1) y el segundo es: \(num2)")

Dado un número, imprimir el último dígito:

func ultimo(_ num:Int) -> Int {
    return num % 10
}
let num = 2479
print("La última cifra de \(num) es \(ultimo(num))")

Dada la edad de un perro, calcular la edad equivalente en humanos, teniendo en cuenta que un año para una persona equivale a 7 años para un perro:

func edadHumano(_ edadPerro:Int) -> Int {
    return edadPerro * 7
}
let edadPerro = 7
print("\(edadPerro) años de un perro equivalen a \(edadHumano(edadPerro)) años de un humano")

Funciones y control de flujo

Dado un número, comprobar si es par o no:

func esPar(_ num:Int) -> Bool {
    return num % 2 == 0
}

let num = 24

// Con if
if esPar(num) {
    print("El numero \(num) es par")
}
else {
    print("El numero \(num) es impar")
}

// Con el operador ?
print("El numero \(num) es " + (esPar(num) ? "par" : "impar"))

Dados dos números, comprobar si el primero es divisible entre el segundo:

func esDivisible(_ num1:Int, _ num2:Int) -> Bool {
    return num2 != 0 && num1 % num2 == 0
}

let num1 = 3
let num2 = 1

// Con if
if esDivisible(num1, num2) {
    print("El número \(num1) es divisible entre \(num2)")
}
else {
    print("El número \(num1) no es divisible entre \(num2)")
}

// Con el operador ?
print("El número \(num1) \(esDivisible(num1, num2) ? "" : "no") es divisible entre \(num2)")

Funciones y tuplas

Dada una cantidad determinada de chicos y chicas, calcular el porcentaje de cada uno respecto al total:

func calcularPorcentajes(hombres:Int, mujeres:Int) -> (hombres:Int, mujeres:Int) {
    let total = hombres + mujeres
    let porcentajeHombres = (hombres * 100) / total
    let porcentajeMujeres = (mujeres * 100) / total

    return (porcentajeHombres, porcentajeMujeres)
}

let hombres = 12
let mujeres = 15
let porcentaje = calcularPorcentajes(hombres:12, mujeres:15)

print("De \(hombres + mujeres) personas, \(mujeres) son mujeres y \(hombres) son hombres, es decir, \(porcentaje.hombres)% de hombres y \(porcentaje.mujeres)% de mujeres")

Disponemos de dos tuplas de tipo (Int,Int) que representan dos fracciones. El primer valor en cada tupla representa el numerador y el segundo valor representa el denominador. Utilizando una función, crear una nueva tupla también de tipo (Int,Int) que contenga el resultado de sumar las dos fracciones:

func suma(_ fraccion1:(Int, Int), _ fraccion2:(Int, Int)) -> (Int, Int) {
    let numerador = fraccion1.0 * fraccion2.1 + fraccion2.0 * fraccion1.1
    let denominador = fraccion1.1 * fraccion2.1 

    return (numerador, denominador)
}

let fraccion1 = (1, 2)
let fraccion2 = (2, 3)
print("La suma de \(fraccion1) y \(fraccion2) es \(suma(fraccion1, fraccion2)))

Escribir el código necesario para jugar a piedra, papel, tijeras, cumpliendo las siguientes especificaciones:

  • Se debe definir una enumeración con tres posibles casos:  .piedra.papel.tijeras.
  • Se debe definir una segunda enumeración con tres posibles casos:  .gana.empata.pierde.
  • Escribir una función que reciba la elección de cada jugador y devuelva el resultado correspondiente al primer jugador.
enum Mano {
    case piedra, papel, tijeras
}

enum Resultado {
    case gana, empata, pierde
}

func jugar(jugador1:Mano, jugador2:Mano) -> Resultado {
    if jugador1 == jugador2 { return .empata }

    if jugador1 == .piedra  && jugador2 == .tijeras || 
       jugador1 == .papel   && jugador2 == .piedra  ||
       jugador1 == .tijeras && jugador2 == .papel { return .gana }

    return .pierde
}

print(jugar(jugador1:.papel, jugador2:.tijeras))

Parámetros variádicos

Calcular la suma de un número indeterminado de fracciones:

func suma(_ fracciones:(Int, Int)...) -> (Int, Int) {
    var resultado = (0, 1)

    for fraccion in fracciones {
        resultado.1 *= fraccion.1
    }
    for fraccion in fracciones {
        resultado.0 += (resultado.1 / fraccion.1) * fraccion.0
    }    

    return resultado
}

print(suma((1,2), (3,4), (5,2), (4,3)))

Swift – Ejercicios resueltos (I)

Variables, constantes y operadores aritméticos

Dadas dos variables, calcular la suma, almacenar el resultado en otra variable e imprimir el resultado:

let num1 = 5
let num2 = 8
let suma = num1 + num2
print("\(num1) + \(num2) = \(suma)")

Calcular el número de segundos en un año almacenando dicho valor en una variable:

let dias = 365
let horas = 24
let segundos = 3600
let segundosAnyo = dias * horas * segundos
print("La cantidad de segundos en un año es \(segundosAnyo)")

Partiendo de la anchura y la altura de una pantalla en píxeles, calcular el número total de píxeles de dicha pantalla guardando el resultado en otra variable:

let alto = 720
let ancho = 480
let pixeles = alto * ancho
print("La cantidad de pixeles es \(pixeles)")

Dadas dos variables, intercambiar sus valores:

var num1 = 4
var num2 = 8

let aux = num2
num2 = num1
num1 = aux

print("El primer numero es: \(num1) y el segundo es: \(num2)")

Dado un número, imprimir el último dígito:

let num = 2479
print("La última cifra de \(num) es \(num % 10)")

Dada la edad de un perro, imprimir la edad equivalente en humanos, teniendo en cuenta que un año para una persona equivale a 7 años para un perro:

let edadPerro = 7
let edadHumano = edadPerro * 7
print("\(edadPerro) años de un perro equivalen a \(edadHumano) años de un humano")

Dada una cantidad determinada de chicos y chicas, imprimir el porcentaje de cada uno respecto al total:

let hombres = 12
let mujeres = 15
let total = hombres + mujeres

let porcentajeHombres = (hombres * 100) / total
let porcentajeMujeres = (hombres * 100) / total

print("De \(total) alumnos, \(mujeres) son mujeres y \(hombres) son hombres, es decir, \(porcentajeHombres)% de hombres y \(mujeres)% de mujeres")

Control de flujo

Dados dos números, compararlos e imprimir el mayor de ellos:

let num1 = 3
let num2 = 4
if num1 > num2 {
    print("El número \(num1) es mayor que \(num2)")
}
else if num2 > num1 {
    print("El número \(num2) es mayor que \(num1)")
}

Dado un número, imprimir un mensaje indicando si es par o impar:

let num = 23

// Con if
if num % 2 == 0 {
    print("El numero \(num) es par")
}
else {
    print("El numero \(num) es impar")
}

// Con el operador ?
print("El numero \(num) es " + (num % 2 == 0 ? "par" : "impar"))

Dados dos números, imprimir un mensaje diciendo si el primero es divisible entre el segundo o no:

let num1 = 3
let num2 = 1

if num2 != 0 && num1 % num2 == 0  {
    print("El número \(num1) es divisible entre \(num2)")
}
else if num2 != 0 && num1 % num2 != 0 {
    print("El número \(num1) no es divisible entre \(num2)")
}
else {
    print("No se puede dividir entre 0")
}

Dadas tres variables, comparar el valor que contienen e indicar si todas ellas son diferentes, o si por el contrario, dos variables o las tres contienen el mismo valor:

var a = 2
var b = 2
var c = 2

if (a == b) || (a == c) || (b == c) {
    print("Al menos dos variables tienen el mismo valor")
} else {
    print("Todas las variables tienen valores diferentes")
}

Disponemos de dos variables que indican los días que hace que hemos comprado leche y huevos respectivamente. Si hace más de 2 días que compramos la leche deberemos imprimir que ya está caducada, y si hace más de 7 días que compramos los huevos, imprimiremos que están caducados. Si la leche y los huevos no estén caducados, se deberá imprimir un mensaje indicando que ambos están en buen estado:

var diasLeche = 6
var diasHuevos = 12

if diasLeche <= 2 && diasHuevos <= 7 {
    print("La leche y los huevos están en buen estado")
} else {
    if diasLeche > 2 {
        print("La leche está caducada")
    }
    if diasHuevos > 7 {
        print("Los huevos están caducados")
    }
}

Dado un entero que indique un año cualquiera, indicar si dicho año es bisiesto o no, teniendo en cuenta que un año es bisiesto si es divisible entre 4 y no es divisible entre 100, y también si es divisible entre 400 (2000 y 2400 sí son bisiestos. 2100, 2200 y 2300 no lo son):

let anyo = 2018

let divisibleEntre4 = anyo % 4 == 0
let divisibleEntre100 = anyo % 100 == 0
let divisibleEntre400 = anyo % 400 == 0

if divisibleEntre4 && (!divisibleEntre100 || divisibleEntre400) {
    print("El año \(anyo) es bisiesto")    
}
else {
    print("El año \(anyo) no es bisiesto") 
}

Generar un número aleatorio entre 1 y 3 (ambos incluidos) simulando que lanzamos una moneda, de forma que se imprima uno de los tres posibles valores (cara, cruz o canto). Se puede utilizar la función random de la siguiente forma: Int.random(in:1...3)

let moneda = Int.random(in:1...3)

if      moneda == 1 { print("Cara")  } 
else if moneda == 2 { print("Cruz")  } 
else                { print("Canto") }

Dados cuatro valores numéricos, imprimir el más pequeño:

let a = 7, b = 4, c = 2, d = 6

var min = a

if b < min { min = b }

if c < min { min = c }

if d < min { min = d }

print(min)

Imprimir si un número es divisible entre 3 y 5 al mismo tiempo:

let numero = 135

// Con if
if numero % 3 == 0 && numero % 5 == 0 {
    print("\(numero) es divisible entre 3 y 5")
} else {
    print("\(numero) no es divisible entre 3 y 5")
}

// Con el operador ?
print("\(numero) \(numero % 3 == 0 && numero % 5 == 0 ? "" : "no") es divisible entre 3 y 5")

Escribir un switch que imprima las siguientes cadenas utilizando los siguientes intervalos dependiendo de una variable que contiene una distancia:

  1. «Aquí» si la distancia es 0.
  2. «Bastante cerca» si la distancia es mayor que 0 y menor que 5.
  3. «Cerca» si la distancia está entre 5 y 15.
  4. «Más o menos cerca» si la distancia está entre 15 y 40, ambos incluidos.
  5. «Lejos» si la distancia es mayor que 40.
let distancia:UInt = 15

switch distancia {
    case 0:
        print("Aquí")
    case 1..<5:
        print("Bastante cerca")
    case 5..<15:
        print("Cerca")
    case 15...40:
        print("Más o menos cerca")
    default:
        print("Lejos")
}

Tenemos una cadena que indica la descripción de un animal. Si el animal es un «gato» debemos imprimir un mensaje indicándolo. Si es un «gato muy grande», debemos decir que el animal tal vez sea un tigre, y en caso contrario indicaremos que es otro tipo de animal:

let animal = "gato muy grande"

switch animal {
    case "gato":
        print("El animal es un gato")
    case "gato muy grande":
        print("El animal tal vez es un tigre")
    default:
        print("El animal no es ni un gato ni un tigre")
}

Utilizando un switch imprimir la cadena «Eres un superhéroe» si tu nombre es «Clark Kent» o «Bruce Wayne», y «Eres una persona normal» en caso contrario:

let nombre = "Bruce Wayne"

switch nombre {
    case "Clark Kent", "Bruce Wayne":
        print("Eres un superhéroe")
    default:
        print("Eres una persona normal")
}

Arrays

Crear de dos formas diferentes un array que contenga los siguientes enteros 5, 8, 10, 16, 23, 40:

var array1: [Int] = [5, 8, 10, 16, 23, 40]
var array2 = [5, 8, 10, 16, 23, 40]
print(array1)
print(array2)

Crear de dos formas diferentes un array que no pueda ser modificado y que contenga los siguientes enteros: 5, 8, 10, 16, 23, 40

let array1: [Int] = [5, 8, 10, 16, 23, 40]
let array2 = [5, 8, 10, 16, 23, 40]
print(array1)
print(array2)

Añade de dos formas diferentes los valores 118, 137, y 5615 al final del siguiente array: [5, 8, 10, 16, 23, 40]

var array1 = [5, 8, 10, 16, 23, 40]
var array2 = [5, 8, 10, 16, 23, 40]
let array3 = [118, 137, 5615]

array1.append(contentsOf: array3)
array2 += array3

print(array1)
print(array2)

Reemplazar el valor 16 con el valor 45 en el siguiente array: [5, 8, 10, 16, 23, 40]

var array = [5, 8, 10, 16, 23, 40]

array[3] = 45 

print(array)

Reemplaza los valores 8, 10, y 16 con los valores 1, 2, 3, 4, 5 y 6 en el siguiente array: [5, 8, 10, 16, 23, 40]

var array = [5, 8, 10, 16, 23, 40]

array[1...3] = [1, 2, 3, 4, 5, 6]

print(array)

Enumeraciones y tuplas

Definir un array de tuplas que tengan 2 campos cada una, para guardar el nombre y primer apellido de 5 personas:

var personas = [(nombre: "Juan", apellido: "Rubio"),
                (nombre: "Pepe", apellido: "García"),
                (nombre: "Luis Enrique", apellido: "Peinado"),
                (nombre: "Álvaro", apellido: "Sánchez"),
                (nombre: "Pablo", apellido: "Ruiz")]                          

for persona in personas { print(persona) }

Partiendo de dos valores cualesquiera, formar una tupla indicando cuál de esos dos valores es el menor y cuál el mayor:

let a = 5, b = 6
let resultado = a < b ? (menor:a, mayor:b) : (menor:b, mayor:a)
print(resultado)

Definir una enumeración para tener un listado de varios tipos de dispositivos de Apple:

enum DispositivoApple {
    case iPhone
    case iPad
    case iWatch
}

let miDispositivo = DispositivoApple.iPhone
print(miDispositivo)

Disponemos de un array que indica los movimientos del personaje de un videojuego, y también conocemos su posición original. Un paso .arriba incrementará la coordenada y en 1. Un paso .abajo decrementará la coordenada y en 1. Un paso a la  .derecha incrementará la coordenada x en 1. Un paso a la .izquierda decrementará la coordenada x en 1. Se debe imprimir la ubicación final del personaje después de realizar los movimientos indicados:

enum Direccion {
    case arriba, abajo, izquierda, derecha
}

var ubicacion = (x: 0, y: 0)
let pasos:[Direccion] = [.abajo, .abajo, .derecha, .arriba]

for paso in pasos {
    switch paso {
        case .arriba:    ubicacion.y += 1
        case .abajo:     ubicacion.y -= 1
        case .derecha:   ubicacion.x += 1
        case .izquierda: ubicacion.x -= 1
    }
}

print(ubicacion)

Escribir el código necesario para jugar a piedra, papel, tijeras, utilizando una enumeración con los tres posibles casos:  .piedra.papel.tijeras:

enum Mano {
    case piedra, papel, tijeras
}

let jugador1 = Mano.papel
let jugador2 = Mano.tijeras

if jugador1 == jugador2 { 
    print("Empate") 
}
else if jugador1 == .piedra  && jugador2 == .tijeras || 
        jugador1 == .papel   && jugador2 == .piedra  ||
        jugador1 == .tijeras && jugador2 == .papel { 
    print("Gana jugador 1") 
}
else { 
    print("Pierde jugador 1") 
}

Utilizando una enumeración de tipos de monedas y un array de tuplas que especifique el número de monedas que tenemos de cada tipo, calcular e imprimir el cantidad total de dinero:

enum TipoMoneda {
    case cents5, cents10, cents20, cents50, euro1, euro2
}

let monedas:[(cantidad:Int,tipo:TipoMoneda)] = [(2,.cents10), (5,.euro1), (3,.euro2)]

var total = 0

for (cantidad, tipo) in monedas {
    switch tipo {
        case .cents5:
            print("\(cantidad) monedas de 5 céntimos")
            total += cantidad * 5
        case .cents10:
            print("\(cantidad) monedas de 10 céntimos")
            total += cantidad * 10
        case .cents20:
            print("\(cantidad) monedas de 20 céntimos")
            total += cantidad * 20
        case .cents50:
            print("\(cantidad) monedas de 50 céntimos")
            total += cantidad * 50
        case .euro1:
            print("\(cantidad) monedas de 1 euro")
            total += cantidad * 100
        case .euro2:
            print("\(cantidad) monedas de 2 euros")
            total += cantidad * 200
    }
}

print("Total: \(Double(total)/100.0) euros")

Disponemos de un array con los bocadillos que quieren pedir unos amigos en un bar. Antes de llamar al camarero se debe crear otro array que contenga tuplas indicando el tipo de bocadillo y el número de bocadillos que quieren pedir de ese tipo:

enum TipoBocadillo {
    case tortilla, calamares, queso
}

let bocadillos:[TipoBocadillo] = [.tortilla, .tortilla, .calamares, .queso, .calamares, .calamares, .queso]

var listaCamarero:[(tipo:TipoBocadillo, cantidad:Int)] = []

for bocadillo in bocadillos {
    var i = 0
    while i < listaCamarero.count {
        if (listaCamarero[i].tipo == bocadillo) {
            listaCamarero[i].cantidad += 1
            break
        }
        i += 1
    } 
    if i >= listaCamarero.count {
        listaCamarero += [(bocadillo, 1)]        
    }
}

for bocadillo in listaCamarero {
    print("\(bocadillo.cantidad) bocadillos de \(bocadillo.tipo)")
}

Swift – Enumeraciones

Definición

Las enumeraciones son un tipo de dato que permiten definir un grupo de valores relacionados bajo un nombre. Cada uno de estos valores dentro de la enumeración son llamados miembros o casos y poseen a su vez, un nombre que los identifica. Esto es, al definir una enumeración, se establecen los posibles estados que ese enum puede llegar a valer. Todos esos estados, o miembros, están relacionados entre sí.

Por ejemplo, si quisiera crear una variable sobre los puntos cardinales sabemos que, en principio, tiene cuatro posibles valores: Norte, Sur, Este y Oeste. Esos cuatro valores podrían ser miembros de un enum llamado PuntosCardinales y la variable a crear podría tomar uno de esos estados.

Sintaxis de las enumeraciones en Swift

Para definir una enumeración se utiliza la palabra reservada enum seguida del nombre de la misma.

Luego, el cuerpo de la enumeración se encierra entre llaves, como vemos a continuación:

enum Enumeracion {
    //Cuerpo de la enumeración
}

Para definir los miembros de una enumeración, se utiliza la palabra reservada case. Siguiendo el ejemplo de los puntos cardinales, su estructura sería la siguiente:

enum PuntoCardinal {
    case norte
    case sur
    case este
    case oeste
}

A su vez, cada caso puede definirse en una sola línea, usando una sola vez la palabra case y separando cada miembro por comas:

enum PuntoCardinal {
    case norte, sur, este, oeste
}

Convención de nombres para las enumeraciones en Swift

Al igual que las clases y las estructuras, las enumeraciones también son tipos de datos que creamos en nuestro código. Por lo tanto, el nombre que le asignemos debe comenzar con una mayúscula y, en caso de ser varias palabras, cada una de ellas debe separarse por mayúsculas sin usar guiones bajos u otro símbolo (camel case).

Otro punto a considerar es que el nombre de la enumeración debe estar en singular y nunca en plural. Esto es así ya que las enumeraciones se usan dentro de variables o constantes, por lo tanto solo pueden adoptar 1 valor a la vez. Siguiendo el caso anterior, si creamos una variable para el punto cardinal tendríamos lo siguiente:

var direccion = PuntoCardinal.norte

La lectura de la asignación anterior es muy clara. Se está eligiendo un punto cardinal de un listado de posibles valores y dicho valor se está guardando en la variable dirección.

Uso de las enumeraciones en Swift

Como vimos, tanto las variables como las constantes pueden ser del tipo de una enumeración. En su asignación inicial, debemos especificar el nombre del enum que vamos a usar y el valor que va a tomar dentro de la enumeración.

Supongamos que tenemos la siguiente enumeración:

enum DiaDeLaSemana {
    case lunes, martes, miercoles, jueves, viernes, sabado, domingo
}

Una posible asignación a una variable podría ser la siguiente:

var mañana = DiaDeLaSemana.martes

Como la variable mañana se inicia por primera vez, debemos especificar que su valor va a ser alguno de los contenidos dentro de la enumeración DiaDeLaSemana. Sin embargo, si quisiéramos modificar su valor, ya no es necesario volver a indicar el tipo de dato:

mañana = .miercoles

Como ya sabemos que mañana es un DiaDeLaSemana, directamente podemos asignarle otro valor dentro del enum usando un punto y el nuevo valor.

Comparar valores de las enumeraciones en Swift

Durante nuestro código, una vez que tengamos una variable con un cierto valor de los disponibles de una enumeración, vamos a necesitar preguntar por él para tomar una decisión.

Comparar con if

Podemos usar if para conocer el valor de la variable:

if mañana == DiaDeLaSemana.lunes {
print(«Se termina el fin de semana!»)
}

Asimismo, tal como vimos antes, podemos simplificar el código anterior sin mencionar el tipo de dato:

if mañana == .lunes {
    print("Se termina el fin de semana!")
}

Comparar con switch

Sin embargo, si tenemos una enumeración con varios casos y deseamos codificar un comportamiento para cada uno, usar un if no va a ser la mejor opción. En esos casos, conviene usar switch:

switch mañana {
    case .lunes:
        print("Mañana es lunes")
    case .martes:
        print("Mañana es martes")
    case .miercoles:
        print("Mañana es miércoles")
    case .jueves:
        print("Mañana es jueves")
    case .viernes:
        print("Mañana es viernes")
    case .sabado:
        print("Mañana es sábado")
    case .domingo:
        print("Mañana es domingo")
}
//Devuelve
//Mañana es miércoles

Cómo comentamos en el capítulo de control de flujo, la sentencia Switch necesita que los casos sean exhaustivos, es decir, que se contemplen todos los posibles valores que esa variable pueda tomar. Como la enumeración DiaDeLaSemana solo tiene 7 casos posibles, no hay riesgo de que una variable de ese tipo adopte otro valor distinto a esos 7, por lo tanto no es necesario usar un default.

Sin embargo, si no hiciéramos mención a los 7 valores en los cases, deberíamos incluir un default:

switch mañana {
    case .lunes:
        print(“Mañana es lunes”)
    case .martes:
        print(“Mañana es martes”)
    case .miercoles:
        print(“Mañana es miércoles”)
    case .jueves:
        print(“Mañana es jueves”)
    case .viernes:
        print(“Mañana es viernes”)
    default:
        print(“Mañana comienza el fin de semana”)
}
//Devuelve
//Mañana es miércoles

Enumeraciones con métodos

Al igual que en las clases y structs, podemos definir métodos dentro de los enums. Esta característica muestra la flexibilidad que las enumeraciones tienen en Swift y lo diferencia con las implementaciones en otros lenguajes de programación.

Los métodos que escribamos dentro de un enum siguen las mismas reglas que vimos en el capítulo de funciones.

enum Saiyan {
    case Goku, Vegeta, Gohan, Trunks

    func superPoder() -> String {
        switch self {
            case .Goku, .Gohan:
                return "Kamehameha"
            case .Vegeta, .Trunks:
                return "Garlic Ho"
        }
    }
}

let principeSaiyan = Saiyan.Vegeta
print("\(principeSaiyan.superPoder())")

//Devuelve
//Garlic Ho

Swift – Clases

Introducción

En este capítulo vamos a ver uno de los conceptos más importantes de la programación orientada a objetos: las clases. Su importancia radica en que todo programador debe diseñar un modelo de datos que soporte el comportamiento que requiere un determinado programa para resolver el problema para el cual es creado y las clases son justamente ideales para ello. En el siguiente capítulo vamos a ver las estructuras que tienen un comportamiento similar a las clases, sobre todo en Swift.

Según Wikipedia una clase es “una plantilla para la creación de objetos de datos según un modelo predefinido. Las clases se utilizan para representar entidades o conceptos, como los sustantivos en el lenguaje. Cada clase es un modelo que define un conjunto de variables -el estado, y métodos apropiados para operar con dichos datos -el comportamiento. Cada objeto creado a partir de la clase se denomina instancia de la clase”.

Como bien dice la definición, usamos las clases para definir un concepto. Dicho concepto forma parte del problema que queremos modelar y por lo tanto, del problema que queremos resolver al crear un programa. Para ello, las clases poseen distintos componentes, entre los que se destacan las propiedades y los métodos. Podemos decir que las propiedades son las características que poseen las clases mientras que los métodos son las acciones que podemos realizar.

Cada vez que creamos una clase, en realidad estamos creando un nuevo tipo de dato. Es por esto que se dice que este tipo de lenguajes es un lenguaje tipado, por la posibilidad que da al programador de crear nuevos tipos. Todo nombre de una clase debe, por convención, nombrarse con una mayúscula en su primer letra, mientas que las propiedades y métodos deben comenzar con minúscula.

Sintaxis de una clase

Para crear una clase se utiliza la palabra reservada class seguida del nombre de la misma:

class UnaClase {
}

Las propiedades son variables o constantes creados dentro del ámbito de una clase y su declaración sigue todas las reglas que vimos en los capítulos anteriores.

class Rectangulo {
    let lados = 4
    let alto = 5
    let ancho = 2
}

let figura = Rectangulo()
print("la figura contiene \(figura.lados) lados")

//Devuelve
//la figura contiene 4 lados

Como se aprecia en el ejemplo, para poder instanciar una clase basta con declarar una variable o constante y hacer una asignación usando el nombre de esa clase. En este caso, se usan paréntesis sin parámetros porque no se ha especificado que reciba ninguno. La clase Rectángulo posee tres propiedades numéricas con un valor asignado al momento de la creación de la clase.

Se dice que la constante figura representa un objeto o una instancia de la clase Rectángulo. La diferencia entre una clase y un objeto (o instancia) es que la clase simplemente es una definición, un concepto. Sin embargo, al momento de crear el objeto, se asigna un espacio en memoria para poder guardar todos los elementos que este define y por lo tanto, sus propiedades y métodos pueden ser usados.

Gracias a que figura es una instancia (y no una simple definición como lo es Rectángulo), podemos acceder a su propiedad lados e imprimir el resultado dentro de una cadena con la función print.

Inicialización de una clase

Si quisiéramos que las propiedades de la clase obtengan su valor al momento de crear la instancia, debemos utilizar un inicializador (lo que en otros lenguajes orientados a objetos se lo conoce como constructor). Mediante este elemento podremos, en el ejemplo, dar un valor a las propiedades alto y ancho para crear distintos tamaños de rectángulos al momento de crear una instancia.

Se define inicialización como el proceso de preparar la instancia de una clase. En él, podemos dar valores iniciales a las propiedades de la clase como así también ejecutar código para hacer un seteo inicial. Para poder usarlo, debemos usar un inicializador que se identifica por la palabra reservada init.

class Rectangulo {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(al:Int, an:Int){
        alto = al
        ancho = an
    }
}

let figura = Rectangulo(al: 3, an: 6)
print("La figura mide \(figura.alto) x \(figura.ancho)")

//Devuelve:
//La figura mide 3 x 6

En el ejemplo usamos init para establecer un valor inicial para las propiedades alto y ancho. Luego, en el cuerpo del mismo usamos esos parámetros para asignárselos a las propiedades. Como se puede observar en el print, los valores se guardan en las variables correspondientes.

En el proceso de inicialización, no solamente se puede dar un valor inicial a las variables. Por el contrario, las constantes también pueden ser inicializadas aquí teniendo en cuenta que una vez que termine el proceso de init, las mismas deben quedar todas inicializadas (no pueden haber constantes sin valor) y luego nunca más podrán variar su contenido. Si quedaran constantes sin inicializar, se obtendría un error.

En el caso de querer dejar una variable sin inicializar incluso cuando ya finalizó la etapa del init, esa variable deberá ser un opcional, ya que en su ciclo de vida existen etapas donde su valor es nil.

Self

Si bien el ejemplo anterior obtiene perfectamente dos valores y los mismos son usados dentro del cuerpo del init, el nombre de los parámetros no siguen los criterios de nomenclatura que promueve Apple. Uno de los puntos más importantes de estos criterios es que el nombre de las variables, parámetros, constantes o cualquier otra estructura que usemos debe dar una idea del valor que contiene o del propósito para el cual fue creado.

En este sentido, los nombres utilizados para los parámetros del ejemplo (al y an) no dicen nada, y una persona que use nuestro código (o bien nosotros mismos más adelante) puede no entender de qué se trata. Por eso, sería más conveniente darles nombres más descriptivos, como alto y ancho respectivamente.

Ahora bien, ¿cómo diferenciamos las referencias alto y ancho de los parámetros con respecto al alto y ancho de las propiedades? Es decir, si en el cuerpo del init hiciéramos un “alto + alto”, ¿estaríamos haciendo referencia a la suma de los alto de los parámetros o de las propiedades?

Para evitar esta confusión existe la palabra reservada self. Con self hacemos referencia a los nombres de las propiedades de la clase. Por lo tanto, dentro de un init en donde el nombre de los parámetros coincide con el de las propiedades, decir self.alto hace referencia al alto de las propiedades, mientras que alto a secas referencia al parámetro.

Dicho esto, el siguiente ejemplo queda mucho más claro en cuanto a los nombres de los parámetros:

class Rectangulo {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
    }
}

let figura = Rectangulo(alto: 3, ancho: 6)
print("La figura mide \(figura.alto) x \(figura.ancho)")

//Devuelve:
//La figura mide 3 x 6

Métodos de una clase

Los métodos son funciones escritas dentro del cuerpo de una clase que permiten darle un comportamiento a ésta, encapsulando tareas y proveyendo de un comportamiento a la instancia.

En Swift los métodos no son un componente exclusivo de las clases sino que también se pueden dotar de métodos a las estructuras y enumeraciones, como veremos más adelante.

Para crear un método, basta con crear una función dentro de la clase:

class Rectangulo {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
    }

    func incrementarAlto() {
        alto += 1
    }
}

let figura = Rectangulo(alto: 3, ancho: 6)
figura.incrementarAlto()
print("La figura mide \(figura.alto) x \(figura.ancho)")

//Devuelve:
//La figura mide 4 x 6

En el ejemplo anterior, creamos un método que nos permite aumentar el alto del rectángulo en 1. Por lo tanto, al invocar a la función incrementarAlto() el valor de la propiedad Alto sube a 4.

Cabe destacar que los métodos siguen todas las reglas que vimos en el capítulo de funciones. Por consiguiente, podemos crear métodos con el mismo nombre pero distinta definición generando sobrecarga o usar etiquetas de argumento, por solo citar dos características:

class Rectangulo {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
    }

    func incrementarAlto() {
        alto += 1
    }

    func incrementarAlto(en valor: Int) {
        alto += valor
    }
}

let figura = Rectangulo(alto: 3, ancho: 6)
figura.incrementarAlto(en: 6)
print("La figura mide \(figura.alto) x \(figura.ancho)")

//Devuelve:
//La figura mide 9 x 6

Herencia

Se trata de una propiedad que permite que una clase se cree a partir de otra, adoptando las características de la clase madre y agregando nuevas funcionalidades. Es especialmente útil cuando se elabora el diagrama de clases que se va a utilizar para resolver un problema determinado y que, consiguientemente, van a definir el comportamiento de la aplicación. Una clase puede heredar métodos, propiedades y otras características de la otra clase.

La herencia es una de las propiedades que diferencia a las clases de otros tipos, como las estructuras o las enumeraciones. Cuando una clase A hereda de B se dice que B es la súper clase y A la subclase.

Las subclases, por lo tanto, pueden hacer uso de la funcionalidad de la súper clase como si dicha funcionalidad se hubiera definido dentro de ella. Asimismo, la subclase puede sobreescribir las versiones de los métodos o propiedades en caso de ser necesario (proceso conocido como overriding).

Clase base

Se define clase base como aquella que no hereda de ninguna otra. Supongamos que tenemos la siguiente clase FiguraGeometrica:

class FiguraGeometrica {
    var color:String
    var area = 0.0
    var perimetro = 0.0

    init(color:String) {
        self.color = color
    }

    func calcularArea() {
        //No escribimos nada ya que cada figura tiene su propia fórmula
    }

    func calcularPerimetro() {
        //No escribimos nada ya que cada figura tiene su propia fórmula
    }

    func cambiar(color:String) {
        self.color = color
    }
}

En el ejemplo, la clase FiguraGeometrica define 3 propiedades: color, área y perímetro. El color es asignado en el init mientras que los otros dos son iniciados en 0. Por otro lado, se definen 3 métodos, pero dos de ellos no tienen código en su interior debido a que no disponen de los datos suficientes para poder hacer el cálculo, ya que cada figura geométrica posee su propia fórmula para calcular estas propiedades. Mientras tanto, lo único que sí sabemos a esta altura es que si se desea cambiar el color, simplemente recibiendo un nuevo color ya es suficiente para hacer el cambio.

La primera conclusión que sacamos es que en la clase base debemos incluir aquellas propiedades y métodos que son comunes en todas las clases heredadas.

Al momento de crear la clase FiguraGeometrica definimos todas las características comunes para una figura geométrica arbitraria. Para darle más comportamiento y un contexto más rico, es necesario crear clases más específicas que hereden de ella.

Para representar colores en una aplicación real se utilizan otras clases en lugar de String, pero para efectos del ejemplo vamos a usarlos como si solo se trataran de cadena de caracteres.

Subclases

Como ya dijimos, al crear una subclase se genera una nueva clase basada en otra, de la cual se heredan sus características. Estas pueden ser usadas como si fueran propias, pueden ser modificadas e incluso pueden agregarse nuevas características.

Para indicar que una subclase hereda de otra, se lo debe indicar al momento de definirla, usando : y el nombre de la súper clase:

class subClase: superClase {

}

Podemos tomar la clase Rectángulo usada en los ejemplos anteriores y hacer que herede de FiguraGeometrica:

class Rectangulo:FiguraGeometrica {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(color:String, alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
        super.init(color: color)
    }

    func incrementarAlto() {
        alto += 1
    }

    func incrementarAlto(en valor: Int) {
        alto += valor
    }
}

Un punto a tener en cuenta es que la súper clase tiene un inicializador distinto al de la subclase (espera un color mientras que la subclase espera un alto y un ancho). Por lo tanto, en el init de la clase Rectángulo se debe invocar al init de la súper clase como última sentencia, utilizando la palabra reservada super.

let rectangulo = Rectangulo(color: "Azul", alto: 9, ancho: 4)
print("El rectángulo mide \(rectangulo.alto) x \(rectangulo.ancho) y es de color \(rectangulo.color)")

//Devuelve:
//El rectángulo mide 9 x 4 y es de color Azul

Como se aprecia en el ejemplo, la variable rectángulo es una instancia de la clase Rectángulo y si bien la propiedad color pertenece a la súper clase, se utiliza como si hubiera sido definido dentro de la sub clase.

De la misma manera, se pueden crear otras subclases adicionales:

class Circulo:FiguraGeometrica {
    var radio: Double

    init(color: String, radio:Double) {
        self.radio = radio
        super.init(color: color)
    }
}

Una clase puede tener n cantidad de subclases, pero una subclase solo puede tener una súper clase.

Override

En el proceso de override se sobreescriben las funcionalidades de los métodos (lo mismo de las propiedades) para proveer una implementación alternativa en la subclase.

class Rectangulo:FiguraGeometrica {
    let lados = 4
    var alto:Int
    var ancho:Int

    init(color:String, alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
        super.init(color: color)
    }

    func incrementarAlto() {
        alto += 1
    }

    func incrementarAlto(en valor: Int) {
        alto += valor
    }

    override func calcularArea() {
        area = Double(alto) * Double(ancho)
    }

    override func calcularPerimetro() {
        perimetro = 2 * Double(alto) + 2 * Double(ancho)
    }
}

class Circulo:FiguraGeometrica {
    var radio: Double

    init(color: String, radio:Double) {
        self.radio = radio
        super.init(color: color)
    }

    override func calcularArea() {
        area = Double.pi * radio * radio
    }

    override func calcularPerimetro() {
        perimetro = 2 * Double.pi * radio
    }
}

Tanto la clase Rectangulo como Circulo ahora tienen sus propias implementaciones de los métodos calcularArea() y calcularPerimetro(), los cuales difieren entre sí ya que sus fórmulas son distintas. Para sobreescribir la versión de la súper clase se usa la palabra reservada override.

let circulo = Circulo(color: "verde", radio: 6.43)
circulo.calcularArea()
circulo.calcularPerimetro()
print("El area del círculo es \(circulo.area) y el perimetro es \(circulo.perimetro)")

//Devuelve:
//El area del círculo es 129.888834103405 y el perímetro es 40.4008815251647

Ejemplos completos

class UnaClase {
    
}

class Rectángulo1 {
    let lados = 4
    let alto = 5
    let ancho = 2
}

let figura1 = Rectángulo1()
print("la figura contiene \(figura1.lados) lados")
//Devuelve
//la figura contiene 4 lados

class FiguraGeometrica {
    var color:String
    var area = 0.0
    var perimetro = 0.0
    
    init(color:String) {
        self.color = color
    }
    
    func calcularArea() {
        //No escribimos nada ya que cada figura tiene su propia fórmula
    }
    
    func calcularPerimetro() {
        //No escribimos nada ya que cada figura tiene su propia fórmula
    }
    
    func cambiar(color:String) {
        self.color = color
    }
    
}


class Rectángulo2 {
    let lados = 4
    var alto:Int
    var ancho:Int
    
    init(alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
    }
    
    func incrementarAlto() {
        alto += 1;
    }
    
    func incrementarAlto(en valor: Int) {
        alto += valor;
    }
}

let figura = Rectángulo2(alto: 3, ancho: 6)
figura.incrementarAlto()
figura.incrementarAlto(en: 6)
print("La figura mide \(figura.ancho) x \(figura.alto)")
//Devuelve:
//La figura mide 6 x 3


/*class subClase: superClase {
    
}
*/


class Rectángulo:FiguraGeometrica {
    let lados = 4
    var alto:Int
    var ancho:Int
    
    init(color:String, alto:Int, ancho:Int){
        self.alto = alto
        self.ancho = ancho
        super.init(color: color)
    }
    
    func incrementarAlto() {
        alto += 1;
    }
    
    func incrementarAlto(en valor: Int) {
        alto += valor;
    }
    
    override func calcularArea() {
        area = Double(alto) * Double(ancho)
    }
    
    override func calcularPerimetro() {
        perimetro = 2 * Double(alto) + 2 * Double(ancho)
    }
}

let rectangulo = Rectángulo(color: "Azul", alto: 9, ancho: 4)
print("El rectángulo mide \(rectangulo.ancho) x \(rectangulo.alto) y es de color \(rectangulo.color)")
//Devuelve:
//El rectángulo mide 4 x 9 y es de color Azul

class Círculo:FiguraGeometrica {
    var radio: Double
    
    init(color: String, radio:Double) {
        self.radio = radio
        super.init(color: color)
    }
    
    override func calcularArea() {
        area = Double.pi * radio * radio
    }
    
    override func calcularPerimetro() {
        perimetro = 2 * Double.pi * radio
    }
}

let círculo = Círculo(color: "verde", radio: 6.43)
círculo.calcularArea()
círculo.calcularPerimetro()
print("El area del círculo es \(círculo.area) y el perímetro es \(círculo.perimetro)")
//Devuelve:
//El area del círculo es 129.888834103405 y el perímetro es 40.4008815251647

Swift – Funciones

Concepto de función

Al igual que en cualquier otro lenguaje de programación, uno de los puntos básicos que todo desarrollador Swift debe conocer es la manera de trabajar con funciones.

Podríamos decir que forma parte del núcleo de un lenguaje y provee un sinfín de beneficios al momento de escribir código claro y mantenible.

Una función es una porción de código que se encapsula bajo un nombre y puede ser invocado las veces que sea necesario.

La primer ventaja que ofrece es la posibilidad de reutilizar esa funcionalidad evitando tener que escribir el algoritmo repetidas veces a lo largo del programa. Se espera que el nombre asignado a la función sea descriptivo y relacionado a la tarea que la misma realiza.

La forma en la que las funciones están pensadas en Swift le da al desarrollador una flexibilidad considerable al momento de utilizarlas.

En este sentido, es posible escribir funciones súper básicas como así también otras mucho más rebuscadas.

Para poder escribir una función, se utiliza la palabra reservada funcseguida del nombre de la misma. La forma más básica sería:

func saludar() {
print("Hola mundo!")
}

saludar()

//Devuelve:
//Hola mundo!

Parámetros y valores de retorno

Asimismo, las funciones pueden recibir valores de entrada para poder trabajar en función a ellos, los cuales son conocidos como parámetros. De la misma manera, una función puede devolver un valor a quien lo invoca una vez finalizada su ejecución, conocido como valor de retorno.

//Función con un parámetro
func saludar(nombre: String) {
print("Hola \(nombre)!")
}

saludar(nombre: "Gabriel")

//Devuelve:
//Hola Gabriel!

//Función con un parámetro y un valor de retorno
func saludarA(nombre: String) -> String {
return "Hola \(nombre)!"
}

let saludo = saludarA(nombre: "Gabriel")
print(saludo)

//Devuelve:
//Hola Gabriel!

Como muestran los ejemplos anteriores, la invocación a una función se realiza escribiendo el nombre de la misma seguido de paréntesis. Si la función posee parámetros, los mismos se incluyen dentro de los paréntesis y si la función devuelve un valor se utiliza la flecha -> seguida del tipo de dato a devolver. Este tipo puede ser cualquiera, ya sea un StringInt, una tupla, una clase o struct que definamos nosotros, un opcional, etc. Asimismo, se debe utilizar algún mecanismo para recibir ese dato (por ejemplo, guardarlo en una constante o variable).

Los valores de entrada pasados a una función al momento de invocarla se denominan Argumentos. Estos argumentos deben coincidir con el tipo de dato de los parámetros esperados de la función.
Por otro lado, el conjunto conformado por el nombre de la función, los parámetros y su valor de retorno conforman la definición de la función, y no puede haber en un mismo código dos funciones con la misma definición.
Cuando tenemos dos o más funciones con el mismo nombre pero distinta definición, decimos que esa función está sobrecargada o que existe una sobrecarga de la misma.

En el caso en que se desee usar dos o más parámetros, se deben separar por una coma en la definición de la función:

//Función con dos parámetros
func saludar(nombre: String, edad: Int) {
print("Felices \(edad) años \(nombre)!")
}

saludar(nombre: "Gabriel", edad: 29)

//Devuelve
//Felices 29 años Gabriel!

Devolver más de un dato

La forma que nos brinda Swift para retornar más de un valor es mediante las tuplas. En este caso, al momento de invocar la función podemos hacer referencia a cualquiera de los datos obtenidos simplemente utilizando su nombre o posición en la tupla:

//Función que devuelve más de un dato
func minMax(array: [Int]) -> (min: Int, max: Int) {
var minimo = array[0]
var maximo = array[0]

for valor in array[1..<array.count] {
if valor < minimo { minimo = valor } else if valor > maximo {
maximo = valor
}
}

return (minimo, maximo)
}

let notas = minMax(array: [1, 2, 10, 8, 5])
print("La nota mas baja es \(notas.min) y la mas alta es \(notas.max)")

//Devuelve:
//La nota más baja es 1 y la más alta es 10

Etiquetas de argumento

Cuando especificamos nuestros parámetros podemos definir una etiqueta de argumento (argument label), la cual se utiliza al momento de llamar a la función y se especifica delante del nombre del parámetro en la definición de la misma. En este sentido, el parámetro se usa internamente en la implementación de la función cuando se requiere hacer uso de él.

//Etiquetas de argumento
func saludar(a nombre: String, porCumplir edad: Int){
print("Felices \(edad) años \(nombre)!")
}

saludar(a: "Gabriel", porCumplir: 30)

//Devuelve:
//Felices 30 años Gabriel!

Como se aprecia en el ejemplo, se está usando un argument label por cada parámetro. Internamente en la función se usa el nombre del parámetro, pero al momento de invocarla solo se usan las etiquetas de argumento. Nótese lo claro que resulta entender lo que hace la función saludar(a:porCumplir:) al momento de invocarla.

En el caso de que no se especifique una etiqueta de argumento, el mismo nombre de parámetro funciona como nombre de parámetro y etiqueta. Sin embargo, si se desea omitir por completo la etiqueta al momento de invocar a la función, se utiliza un guión bajo (_):

//Omitiendo argument label
func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int
{
return primerNumero + segundoNumero
}

print("El resultado de 4 + 5 es \(sumar(4,5))")

//Devuelve:
//El resultado de 4 + 5 es 9

Parámetros con valores por defecto

Otra posibilidad que nos habilita Swift es la de asignar un valor por defecto a los parámetros, de forma tal que obtengan ese valor en caso de que no se le pase ninguno al momento de invocar a la función.

En el caso de utilizar esta metodología, se recomienda listar primero los parámetros que no tienen valores por defecto (que en la mayoría de los casos son más representativos para la función) y luego los que sí tienen.

//Parámetros por defecto
func saludar(a nombre:String, cumpleAños:Bool = false) {
if cumpleAños {
print("Feliz cumpleaños \(nombre)")
}
else {
print("Buen día \(nombre)")
}
}

saludar(a: "Gabriel", cumpleAños: true)
saludar(a: "Gabriel")

//Devuelve:
//Feliz cumpleaños Gabriel
//Buen día Gabriel

Como se aprecia en el ejemplo anterior, al tener un parámetro opcional la función puede ser llamada incluyendo ese argumento o no.

Parámetros variádicos

Este tipo de parámetros permite especificar una cantidad de cero o más valores de un tipo determinado. Se utiliza para dar flexibilidad al momento de usar la función para pasar una cantidad variables de valores sabiendo que la misma siempre va a trabajar correctamente.

Para indicar que un parámetro es variádico, se usan 3 puntos suspensivos luego del tipo del parámetro, e internamente ese valor se usa como un array.

//Parámetros variádicos
func sumar(numeros:Int…) -> Int {
var suma:Int=0

for valor in numeros {
suma = suma+valor
}

return suma
}

print(“La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))”)

//Devuelve:
//La suma es igual a 47

Las funciones pueden tener 1 solo parámetro variádico. En caso de que se necesite usar varios parámetros, siendo uno solo de ellos variádico, éste debe quedar al final.

Parámetros in-out

Los parámetros que usamos en nuestras funciones son constantes, es decir que no pueden ser modificadas internamente en la función. Si por ejemplo quisiéramos agregar un número más al ejemplo anterior, recibiríamos este error:

error: cannot use mutating member on immutable value: ‘numeros’ is a ‘let’ constant

 numeros.append(3)

Esta característica funciona así de manera intencionada para evitar que se cambie el valor de un parámetro por error, lo que puede causar comportamientos extraños a lo largo del código y genere una alta dificultad para encontrar el motivo.

De todas maneras, si lo que queremos es modificar los valores de los parámetros internamente en la función y que ese cambio persista incluso cuando la función termina, debemos usar el modificador inout entre el nombre del parámetro y su tipo.

Existen unas reglas a tener en cuenta:

  • Al momento de invocar la función, si el parámetro es inout, solo pueden pasarse variables. Constantes o literales no están permitidos ya que justamente no pueden ser modificados
  • Los parámetros variádicos no pueden ser inout
  • Los parámetros inout no pueden tener valores por defecto
  • Se debe utilizar un ampersand (&) delante de la variable en el llamado a la función
//Parámetros inout
func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) {
let auxiliar = a
a = b
b = auxiliar
}

var numero1 = 5
var numero2 = 8

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

cambiarDosEnteros(&numero1, &numero2)

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

//Devuelve
//Numero1: 5
//Numero2: 8

//Numero1: 8
//Numero2: 5

El uso del ampersand (&) viene del lenguaje C y se utiliza para indicar la posición en memoria de la variable. Como el objetivo es modificar el valor del parámetro dentro de la función, se pasa su dirección para poder introducir allí el nuevo dato.

Tipo de una función

Todas las funciones tienen su propio tipo, que está formado por el tipo de sus parámetros y el tipo de su valor de retorno. Veamos algunos ejemplos:

func saludar() {
print("Hola mundo!")
}

El tipo de esta función es () -> Void, o lo que es lo mismo “una función que no recibe parámetros y devuelve Void”

func saludarA(nombre: String) -> String {
return "Hola \(nombre)!"
}

El tipo de esta función es (String) -> String, o lo que es lo mismo “una función que recibe un parámetro del tipo String y devuelve un String”

En este sentido, se pueden definir variables o constantes del tipo de una función. Por ejemplo:

//Tipos de una función
func sumarDosNumeros(_ a: Int, _ b: Int) -> Int {
return a + b
}

func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int {
return a * b
}

var calculo:(Int,Int) -> Int = sumarDosNumeros

print("El resultado de sumar 3 + 7 es \(calculo(3,7))")

calculo = multiplicarDosNumeros

print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))")

//Devuelve:
//El resultado de sumar 3 + 7 es 10
//El resultado de multiplicar 3 x 7 es 21

Lo que hicimos fue definir dos funciones distintas pero del mismo tipo: ambas reciben dos parámetros Int y devuelven un Int. Luego, definimos una variable de ese tipo (Int, Int) -> Int al cual primero lo igualamos a la primer función y luego a la segunda.

De la misma manera, se puede utilizar a las funciones como parámetros, definiéndolos con el tipo correspondiente. Esto permite que parte de la implementación de la función se la deje a la clase o struct que la invoca. Más adelante veremos cómo funcionan las clases en swift.

func imprimirResultado(_ operacion:(Int,Int) -> Int, _ a:Int, _ b:Int ) {
print("El resultado es \(operacion(a,b))")
}

imprimirResultado(sumarDosNumeros, 10, 60)

//Devuelve:
//El resultado es 70

En el ejemplo, la función imprimirResultado(_:_:_:) solo hace eso, imprime un resultado de una operación que no se conoce al momento de la implementación pero que se recibe por parámetro. Recién al momento de llamar a la función se sabe que lo que se quiere es sumar los números pasados a la misma.

Ejemplos completos

//Funcion sin parametros
func saludar() {
    print("Hola mundo!")
}

saludar()

//Devuelve:
//Hola mundo!

//Función con un parámetro
func saludar(nombre: String) {
    print("Hola \(nombre)!")
}

saludar(nombre: "Gabriel")

//Devuelve:
//Hola Gabriel!

//Función con un parámetro y un valor de retorno
func saludarA(nombre: String) -> String {
    return "Hola \(nombre)!"
}

let saludo = saludarA(nombre: "Gabriel")
print(saludo)

//Función con dos parámetros
func saludar(nombre: String, edad: Int) {
    
    print("Felices \(edad) años \(nombre)!")
}

saludar(nombre: "Gabriel", edad: 29)

//Devuelve
//Felices 29 años Gabriel!

//Función que devuelve más de un dato
func minMax(array: [Int]) -> (min: Int, max: Int) {
    var minimo = array[0]
    var maximo = array[0]
    for valor in array[1..<array.count] {
        if valor < minimo {
            minimo = valor
        } else if valor > maximo {
            maximo = valor
        }
    }
    return (minimo, maximo)
}

let notas = minMax(array: [1, 2, 10, 8, 5])
print("La nota más baja es \(notas.min) y la más alta es \(notas.max)")

//Devuelve:
//La nota más baja es 1 y la más alta es 10

//Etiquetas de argumento
func saludar(a nombre: String, porCumplir edad: Int){
    print("Felices \(edad) años \(nombre)!")
}

saludar(a: "Gabriel", porCumplir: 30)

//Devuelve:
//Felices 30 años Gabriel!

//Omitiendo argument label
func sumar(_ primerNumero: Int, _ segundoNumero:Int) -> Int
{
    return primerNumero + segundoNumero
}

print("El resultado de 4 + 5 es \(sumar(4,5))")

//Devuelve:
//El resultado de 4 + 5 es 9

//Parámetros por defecto
func saludar(a nombre:String, cumpleAños:Bool = false) {
    if cumpleAños {
        print("Feliz cumpleaños \(nombre)")
    }
    else {
        print("Buen día \(nombre)")
    }
}

saludar(a: "Gabriel", cumpleAños: true)
saludar(a: "Gabriel")

//Devuelve:
//Feliz cumpleaños Gabriel
//Buen día Gabriel

//Parámetros variádicos
func sumar(numeros:Int...) -> Int {
    var suma:Int=0
    for valor in numeros {
        suma = suma+valor
    }
    return suma
}

print("La suma es igual a \(sumar(numeros: 4,6,2,3,10,6,7,5,4))")

//Devuelve:
//La suma es igual a 47

//Parámetros inout
func cambiarDosEnteros(_ a: inout Int, _ b: inout Int) {
    let auxiliar = a
    a = b
    b = auxiliar
}

var numero1=5
var numero2=8

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

cambiarDosEnteros(&numero1, &numero2)

print("Numero1: \(numero1)")
print("Numero2: \(numero2)")

//Devuelve
//Numero1: 5
//Numero2: 8
//Numero1: 8
//Numero2: 5


//Tipos de una función
func sumarDosNumeros(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplicarDosNumeros(_ a: Int, _ b: Int) -> Int {
    return a * b
}

var calculo:(Int,Int) -> Int = sumarDosNumeros

print("El resultado de sumar 3 + 7 es \(calculo(3,7))")

calculo = multiplicarDosNumeros
print("El resultado de multiplicar 3 x 7 es \(calculo(3,7))")

//Devuelve:
//El resultado de sumar 3 + 7 es 10
//El resultado de multiplicar 3 x 7 es 21

func imprimirResultado(_ operacion:(Int,Int)->Int, _ a:Int, _ b:Int ) {
    print("El resultado es \(operacion(a,b))")
}

imprimirResultado(sumarDosNumeros, 10, 60)

//Devuelve:
//El resultado es 70

Swift – Control de flujo

Control de flujo

Uno de los temas que conforman el ABC del aprendizaje de un lenguaje de programación es sin dudas el del control de flujo.

Esto refiere a unos comandos que podemos usar para modificar el orden secuencial de un programa, permitiéndoles saltarse líneas, ejecutar varias veces una misma sentencia, hacer decisiones en base a condiciones, etcétera.

Bucles

Los bucles o loops nos permiten ejecutar un algoritmo una cierta cantidad de veces, dependiendo de la condición que se utilice o el rango de inicio y fin.

For-in

Básicamente existen dos escenarios donde podemos usar este tipo de bucles. El primero se da cuando se necesita recorrer una colección de ítems, como es el caso de un array o un diccionario.

//iteración sobre un array
let array = ["Uno", "Dos", "Tres"]

for item in array {
print("Hola \(item)")
}

//Devuelve:
//Hola Uno
//Hola Dos
//Hola Tres

//Iteración sobre un diccionario
let stock = ["Mouse": 5, "Teclado":20, "Pen drive": 10]

for (dispositivo, cantidad) in stock {
print("Se disponen de \(cantidad) unidades de \(dispositivo)")
}

//Devuelve:
//Se disponen de 5 unidades de Mouse
//Se disponen de 20 unidades de Teclado
//Se disponen de 10 unidades de Pen drive

Como se puede apreciar, al recorrer un diccionario se obtiene, en cada iteración, una tupla (dispositivo, cantidad) al que luego se puede acceder haciendo referencia a sus nombres y solo existe mientras dure el ciclo del for.

El otro escenario donde se usa este tipo de loop se da cuando se conoce el rango sobre el cual se quiere iterar. Por ejemplo, si quisiéramos contar desde 1 a 4 podríamos hacer:

for numero in 1...4 {
print(numero)
}

//Devuelve
//1
//2
//3
//4

En cada ejecución del bucle, la constante numero va tomando los valores del rango, empezando por 1 y terminando por 4. En el ejemplo, se está usando el operador de rango cerrado (…) que implica que se incluye en el rango tanto el 1 como el 4.

Operadores de rango

Como te estarás imaginando, ese no es el único tipo de operador de rango que existe, sino que podemos optar por los siguientes:

  • Operador de rango cerrado (…): es el que ya vimos. Incluye los bordes.
  • Operador de rango semi abierto (..<): incluye el rango de inicio pero excluye el final. Por ejemplo, 1..<4 toma los valores 1, 2 y 3.
  • Operador de rango a un lado (n…): se llama así ya que permite indicar el numero de posición del elemento desde el que se quiere iterar hacia delante lo más posible. Si tomamos el ejemplo del primer array y usamos un rango de (1…) se incluirían los valores “Dos” y “Tres” ya que se está indicando que se comience por el valor de la posición 1. Recordemos que los array comienzan en el índice 0. De la misma manera, se pueden hacer rangos a un lado a la inversa (…n) para indicar que se quiere iterar desde el primer valor hasta la posición n.
  • Operador de rango semi abierto a un lado (..<n): es la combinación de los dos anteriores. Se especifica desde o hasta donde queremos iterar (a un lado) pero dejando el borde abierto.

stride

Existe una manera especial de indicar rangos en la que se permite “saltearse” valores. Por ejemplo, si quisiéramos contar de 2 en 2 desde 0 a 10 podemos usar stride(from:through:by:)

for numero in stride(from: 0, through: 10, by: 2) {
print(numero)
}

//Devuelve:
//0
//2
//4
//6
//8
//10

Si quisiéramos hacer lo mismo pero sin incluir el 10, podemos usar stride(from:to:by:)

for numero in stride(from: 0, to: 10, by: 2) {
print(numero)
}

While

Este tipo de bucle realiza iteraciones hasta que una condición dada devuelva false. Existen dos tipos:

WHILE

Primero se chequea la condición. Si la misma devuelve true, se accede al bucle. La sintaxis es la siguiente:

while condición {
sentencias
}
var contador = 0

while contador < 10 {
print("Contador: \(contador)")
contador += 1
}

//Devuelve:
//Contador: 0
//Contador: 1
//Contador: 2
//Contador: 3
//Contador: 4
//Contador: 5
//Contador: 6
//Contador: 7
//Contador: 8
//Contador: 9

En el ejemplo anterior, se define un contador con un valor inicial de 0. Luego se repiten dos sentencias (se imprime por pantalla el valor del contador y se lo incrementa en 1) siempre que ese contador sea menor a 10. Por lo tanto, el bucle va a ejecutarse hasta que el contador llegue al valor 9. Una vez que valga 10, no va a cumplir la condición del while y por lo tanto no va a volver a ingresar.

Repeat-While

Primero se realiza una iteración del bucle y luego se chequea la condición. Si la misma devuelve true, vuelve a ejecutar el bucle hasta que la condición sea false. Este bucle se utiliza cuando se sabe que por lo menos una vez es necesario ejecutar el algoritmo que contiene. La sintaxis es la siguiente:

repeat {
sentencias
} while condición
contador = 0

repeat {
print("Contador: \(contador)")
contador += 1
} while contador < 10

//Devuelve:
//Contador: 0
//Contador: 1
//Contador: 2
//Contador: 3
//Contador: 4
//Contador: 5
//Contador: 6
//Contador: 7
//Contador: 8
//Contador: 9

Aquí se vuelve a inicializar el contador a 0 y a continuación ya se ejecuta el primer ciclo del bucle, en donde se realizan las dos mismas operaciones que en el ejemplo anterior. La diferencia radica en que en este caso siempre se ejecuta por lo menos una vez el bucle, mientras que en el ejemplo del while solo se ingresa si cumple la condición inicial.

Sentencias condicionales

Este tipo de sentencias nos permiten tomar decisiones a lo largo del programa y ejecutar determinadas líneas de código siempre que se cumpla alguna condición. Existen dos sentencias para ello: if y switch.

If

Esta sentencia se usa para las condiciones más simples que poseen pocas opciones.

let promedio = 9

if promedio >= 7 {
print("Curso aprobado!")
}

//Devuelve:
//Curso aprobado!

En este ejemplo se valida si la constante promedio es mayor o igual a 7. En ese caso se imprime “Curso aprobado!” y en caso contrario no se está realizando ninguna acción.

Asimismo, se pueden anidar varios if en caso de necesitar preguntar por nuevas condiciones.

let alumnos = 20

if alumnos < 10 {
print("Hoy faltaron muchos alumnos")
} else if alumnos < 17 {
print("Vinieron muchos alumnos")
} else {
print("Hoy vinieron todos!")
}

//Devuelve:
//Hoy vinieron todos!

En este ejemplo, se crea una constante con la cantidad de alumnos que vinieron a clase y se le asigna un 20. Luego se pregunta si vinieron menos de 10 alumnos y en caso de ser cierto, se imprime “Hoy faltaron muchos alumnos”. En caso de que esa condición no sea cierta (o sea, si el valor es igual o mayor a 10) se pasa a la segunda condición, donde solo ingresaría si el valor es menor a 17. Como tampoco es correcto, ingresa al else el cual se ejecuta en cualquier otro caso. Como ninguno de los condicionantes anteriores devolvieron true, ya que la cantidad de alumnos es 20, se imprime por pantalla “Hoy vinieron todos!”.

Si bien se pueden anidar varios else-if, no resulta práctico si se tienen muchas condiciones que validar. En ese caso, conviene considerar la posibilidad de usar switch.

Switch

Esta sentencia resulta práctica cuando se tienen que validar una gran cantidad de condiciones. Switch compara un determinado valor con una serie de opciones y en caso de validar alguna o algunas de ellas, se ejecuta el código que la misma contiene.

El uso de esta sentencia en Swift es realmente muy poderoso ya que en cada opción se puede hacer uso de varios patrones y no solamente valores constantes como en otros lenguajes de programación.

La sintaxis de este comando es:

switch valor a considerar {
case valor 1:
sentencias para el valor 1
case valor 2,
valor 3:
sentencias para el valor 2 y 3
default:
caso contrario, hacer otra cosa
}

Como se puede observar, primero se define un valor determinado que luego se va a comparar con otros valores, dentro de cada case. La gran ventaja de Swift es que en cada case no solamente se pueden utilizar valores constantes sino que existe una gran cantidad de patrones que podemos usar, lo cual genera una gran flexibilidad en nuestro código.

Una vez que alguno de los case coincide o “hace match” con el valor comparado, se ejecuta el bloque que el mismo encierra, de la misma manera que sucede con el if.

Un punto a tener en cuenta es que la sentencia switch debe ser exhaustiva. Esto implica que dado un valor a considerar, se deben especificar dentro de los case todos los casos posibles que ese valor puede llegar a tener. En los casos en los que no es posible hacerlo o el programador no tiene intenciones de dar un comportamiento en particular para algún valor determinado, se puede usar la palabra reservada default. Al hacer uso de éste se cubren todos los demás casos no especificados en cada case.

A diferencia de otros lenguajes, no se debe usar break al final de cada case, ya que al ingresar a uno se ejecutan las líneas de código que este contiene y se sale del switch automáticamente.

let personajeAnime = "Goku"

switch personajeAnime {
case "Goku":
print("Kamehameha!!")
case "Vegeta":
print("Final Flash!!")
default:
print("nada")
}

//Devuelve:
//Kamehameha!!

En el ejemplo, se crea un personaje de Anime y se pregunta en cada casepor un personaje en particular. Como el primer case es el que valida la sentencia switch, se ejecuta el print y se devuelve “Kamehameha!!”. Es importante destacar que la sentencia default es obligatorio ponerlo ya que si la constante personajeAnime no vale ni Goku ni Vegeta no se sabría que hacer (definición de exhaustivo). Siempre es obligación cubrir todos los valores posibles.

Fallthrough

Otro punto a tener en cuenta es que cada case debe tener un bloque de ejecución (no puede estar vacío y no contener sentencias en su interior). El siguiente switch no es correcto:

switch personajeAnime {
case "Goku":
case "Gohan":
print("Kamehameha!!")
case "Vegeta":
print("Final Flash!!")
default:
print("nada")
}

//Devuelve el error:
//error: ‘case’ label in a ‘switch’ should have at least one executable statement

En su lugar, se pueden poner dos valores distintos en el mismo case, separado por una coma o usar la palabra fallthrough en el primero de ellos:

switch personajeAnime {
case "Goku":
fallthrough
case "Gohan":
print(“Kamehameha!!”)
case "Vegeta":
print("Final Flash!!")
default:
print("nada")
}

//Devuelve:
//Kamehameha!!

switch personajeAnime {
case "Goku", "Gohan":
print("Kamehameha!!")
case "Vegeta":
print("Final Flash!!")
default:
print("nada")
}

//Devuelve:
//Kamehameha!!

Como vemos, fallthrough hace que al terminar de ejecutar ese casesiga ejecutando el case siguiente.

Comparación por intervalos

En el caso de tener que tomar una acción determinada dependiendo de un valor numérico en el switch, se pueden usar rangos para agrupar acciones dentro de un mismo case. De esta forma, suponiendo que se debe construir un algoritmo que indique si un examen se encuentra aprobado o desaprobado, se puede utilizar las siguientes líneas:

let nota = 5

switch nota {
case 0…6:
print("Examen desaprobado")
case 7…10:
print("Examen aprobado")
default:
print("Nota inexistente")
}

//Devuelve:
//Examen desaprobado

En el ejemplo anterior se utilizan rangos para agrupar las notas comprendidas entre 0 y 6 por un lado, y de 7 a 10 por otro lado. En ambos casos, los extremos del rango están incluidos en el case. Por último, dado que la sentencia switch debe ser completa, es obligatorio el uso del default para cualquier nota menor a 0 o mayor a 10, algo que en la práctica no debería ser posible. Cabe aclarar que cualquier operador de rango visto anteriormente puede ser usado dentro del case.

Vinculación de valores y where

La sentencia switch permite crear constantes o variables temporales en cada case para poder usar el valor que se está comparando dentro del cuerpo de ese case, en caso de entrar al mismo. Esta técnica es conocida como value binding (vinculación de valores). Asimismo, se puede utilizar la sentencia where para agregar condiciones adicionales y hacer más específica la comparación de ese case.

let numero = 18

switch numero {
case let x where x%2 == 0:
print(“Número par: \(x)”)
case let x where x%2 != 0:
print(“Número impar:\(x)”)
default:
print(“Nada”)
}

//Devuelve:
//Número par: 18

En el ejemplo, se crea una constante numero y se le asigna el valor 18. Luego en el primer case se pregunta en el where si ese valor es divisible por 2 para comprobar si es par y en el segundo case, si es impar. En ambos casos, se crea una constante x para almacenar ese valor y luego imprimirlo usando print. Como el numero es par, se obtiene como resultado “Número par: 18”.

break

Como dijimos anteriormente, la sentencia switch debe ser exhaustiva, por lo tanto todos los casos posibles deben ser cubiertos por los case. Sin embargo, en muchas ocasiones no hay necesidad de tomar alguna acción en algunos case y para esto es muy útil la sentencia break. En otras palabras, break nos permite terminar la ejecución del switch para que el flujo del programa continue inmediatamente luego de éste. Se puede decir que ésta es la manera en la que Swift nos permite ignorar determinados case.

De esta manera, una forma más elegante de escribir el código anterior es:

let numero = 18

switch numero {
case let x where x%2 == 0:
print("Número par: \(x)")
case let x where x%2 != 0:
print("Número impar:\(x)")
default:
break
}

//Devuelve:
//Número par: 18

Ejemplos completos

//iteración sobre un arreglo
let array = ["Mundo", "Gabriel", "Vecino"]

for item in array {
    print("Hola \(item)")
}

//Devuelve:
//Hola Mundo
//Hola Gabriel
//Hola Vecino

//Iteración sobre un diccionario
let stock = ["Mouse": 5, "Teclado":20, "Pen drive": 10]

for (dispositivo, cantidad) in stock {
    print("Se disponen de \(cantidad) unidades de \(dispositivo)")
}

//Devuelve:
//Se disponen de 5 unidades de Mouse
//Se disponen de 20 unidades de Teclado
//Se disponen de 10 unidades de Pen drive

for numero in 1...4{
    print(numero)
}

//Devuelve
//1
//2
//3
//4

for numero in stride(from: 0, through: 10, by: 2) {
    print(numero)
}

//Devuelve:
//0
//2
//4
//6
//8
//10

for numero in stride(from: 0, to: 10, by: 2) {
    print(numero)
}


var contador = 0
while contador < 10 {
    print("Contador: \(contador)")
    contador += 1
}

//Devuelve:
//Contador: 0
//Contador: 1
//Contador: 2
//Contador: 3
//Contador: 4
//Contador: 5
//Contador: 6
//Contador: 7
//Contador: 8
//Contador: 9

contador = 0
repeat {
    print("Contador: \(contador)")
    contador += 1
} while contador < 10

//Devuelve:
//Contador: 0
//Contador: 1
//Contador: 2
//Contador: 3
//Contador: 4
//Contador: 5
//Contador: 6
//Contador: 7
//Contador: 8
//Contador: 9

let promedio = 9
if promedio >= 7 {
    print("Curso aprobado!")
}

//Devuelve:
//Curso aprobado!

let alumnos = 20
if alumnos < 10 {
    print("Hoy faltaron muchos alumnos")
} else if alumnos < 17 {
    print("Vinieron muchos alumnos")
} else {
    print("Hoy vinieron todos!")
}

//Devuelve:
//Hoy vinieron todos!


let personajeAnime = "Goku"
switch personajeAnime {
case "Goku":
    print("Kamehameha!!")
case "Vegeta":
    print("Final Flash!!")
default:
    print("nada")
}

//Devuelve:
//Kamehameha!!

switch personajeAnime {
case "Goku":
    fallthrough
case "Gohan":
    print("Kamehameha!!")
case "Vegeta":
    print("Final Flash!!")
default:
    print("nada")
}

//Devuelve:
//Kamehameha!!

switch personajeAnime {
case "Goku", "Gohan":
    print("Kamehameha!!")
case "Vegeta":
    print("Final Flash!!")
default:
    print("nada")
}

//Devuelve:
//Kamehameha!!

let nota = 5
switch nota {
case 0...6:
    print("Examen desaprobado")
case 7...10:
    print("Examen aprobado")
default:
    print("Nota inexistente")
}

//Devuelve:
//Examen desaprobado

let numero = 18
switch numero {
case let x where x%2 == 0:
    print("Número par: \(x)")
case let x where x%2 != 0:
    print("Número impar:\(x)")
default:
    break
}
//Devuelve:
//Número par: 18

Swift – Aspectos básicos

Constantes y variables

Cualquier tipo de aplicación necesita poder guardar datos temporalmente en la memoria para poder trabajar con ellos. Las constantes y variables son apartados reservados en la memoria del dispositivo para que podamos acceder a la información que contienen durante la ejecución del programa.

En este sentido, se define una variable como un espacio en memoria que puede ser modificado por un algoritmo y una constante como una zona de memoria de solo lectura, que no puede ser modificada una vez se le haya asignado un valor inicial.

Para poder utilizar tanto las variables como las constantes, debemos definir un nombre descriptivo de su contenido y debemos asociarles además un tipo de dato según la información que vayan a contener.

En Swift, para definir una variable debemos utilizar la palabra reservada var mientras que para definir una constante, debemos utilizar la palabra reservada let:

let maxAlumnosGrupo = 30
var numAlumnosGrupo = 26

En el ejemplo anterior estamos declarando una constante con la cantidad máxima de alumnos en una clase. Dado que esa cantidad no variará, es conveniente definirla como una constante. Por otro lado, estamos declarando una variable con la cantidad de alumnos que actualmente se encuentran en la clase, y cuyo valor inicial hemos establecido en 26. Este valor puede ir modificándose a medida que transcurre el tiempo, y por ese motivo debemos utilizar obligatoriamente una variable para guardar este dato.

Hemos mencionado además que las variables y las constantes se asocian a nombres, tipos de datos y valores, pero en el ejemplo anterior sin embargo, no hemos especificado el tipo de dato de la variable o la constante. Esto es posible porque Swift utiliza inferencia de tipos, es decir, que “adivina” el tipo de dato que se quiere guardar en memoria utilizando como referencia el valor que le hayamos asignando inicialmente.

Si observamos el ejemplo anterior, en ambos casos asignamos valores numéricos enteros, por lo tanto Swift infiere que ambos son del tipo Int.

En cualquier caso, si quisiéramos indicar nosotros mismos el tipo de dato de manera explícita podríamos especificarlo después del nombre de la variable utilizando los dos puntos (:) como separador, tal como se observa en el siguiente ejemplo:

let maxAlumnosGrupo: Int = 30
var numAlumnosGrupo: Int = 26
let nombreProfesorInformatica: String = "Fernando"

En el ejemplo podemos observar que escribimos el nombre de la variable o constante, seguida de dos puntos, el tipo de dato y por último el valor que queramos asignar inicialmente.

Imprimiendo valores

Para imprimir el valor de una constante o una variable en la consola, podemos utilizar la función  print(_:separator:terminator:):

let nombreProfesorInformatica: String = "Fernando"
print(nombreProfesorInformatica)

// Imprime: Fernando

La función print es global y nos permite imprimir uno o más valores. Posee dos parámetros que tienen valores por defecto, los cuales son separator y terminator. Esto implica que al momento de invocar a la función print, estos parámetros pueden ser omitidos por completo.

En caso de no especificar nada, esta función añade al final un salto de línea. Si se quiere indicar que el cursor se mantenga en la misma línea, se debe pasar un string vacío al parámetro terminator:

let nombreProfesorInformatica = "Fernando"
print(nombreProfesorInformatica, terminator:"")

// Imprime: Fernando

Una técnica muy utilizada a la hora de imprimir valores usando print, es la de interpolación de cadenas. Gracias a ella se puede especificar un espacio dentro de la cadena de texto para que sea reemplazado por el valor de una variable o constante al momento de ejecutarse. Se utiliza una barra invertida y se encierra la variable o constante entre paréntesis:

let maxAlumnosGrupo = 30
var numAlumnosGrupo = 26
print("De \(maxAlumnosGrupo) alumnos matriculados, hoy han venido \(numAlumnosGrupo)")

// Imprime: De 30 alumnos matriculados, hoy han venido 26

A diferencia de otros lenguajes, Swift no requiere el uso del punto y coma al finalizar una sentencia. Sin embargo, está permitido su uso:

let nombreProfesorInformatica = "Fernando";
print(nombreProfesorInformatica);

// Imprime: Fernando

Comentarios

Es muy común escribir texto dentro del código que no queremos que sea ejecutado pero que nos sirve como anotaciones o recordatorios. Estos comentarios son ignorados por el compilador y no son ejecutados.

Existen dos tipos de comentarios. Por un lado, tenemos a los de línea, quienes comienzan con dos barras o diagonales:

// Esto es un comentario de linea

Por otro lado, tenemos los comentarios multilineas, que se encierran entre /* y */ :

/* Esto es un comentario
de
varias
líneas */

Operadores básicos

Los operadores básicos incluidos en Swift son los siguientes:

  • Adición (+)
  • Sustracción (-)
  • Multiplicación (*)
  • División (/)
  • Resto (%) –> hace referencia al resto de una división. Ej: 10/3 es igual a 3 y sobra 1. Por lo tanto, el resto es 1.
  • Asignación (=) -> Se usa para darle un valor a una variable o constante, como vimos en los ejemplos.
  • Igual a (==)
  • Distinto a (!=)
  • Mayor a (>)
  • Menor a (<)
  • Mayor o igual (>=)
  • Menor o igual (<=)

Tipos de datos

En Swift podemos encontrar los siguientes tipos de dato:

  • Números enteros: Int y UInt
  • Números de coma flotante: Float y Double
  • String y Character
  • Bool
  • Tuplas
  • Tipos de Colección: Array, Set y Dictionary

En este post vamos a ver los usos más comunes de cada uno. Cabe mencionar que los tipos de dato se escriben con Mayúsculas la primer letra.

Números enteros

Los números enteros son aquellos que no poseen parte decimal o fracción, como el 1, 2, 3, 58 y -90. Existen dos tipos:

  • Con signo (signed): Son aquellos que pueden ser negativos, cero o positivos. Existen en sus versiones de 8, 16, 32 y 64 bits llamados de la siguiente manera:
    • Int8
    • Int16
    • Int32
    • Int64

Asimismo, Swift provee una versión adicional llamada Int que coincide en tamaño con aquel que posee la misma cantidad de bits que el sistema en donde se está ejecutando. Esto es, si el sistema es de 32 bits, entonces Int tiene el mismo tamaño que Int32 y, si el sistema es de 64, equivale al Int64.

  • Sin signo (unsigned): Son aquellos que pueden ser cero o positivos. De la misma manera que los anteriores, Swift provee el tipo UInt que coincide con UInt32 o UInt64, dependiendo de la cantidad de bits del sistema. Las otras versiones de 8 y 16 bits también están disponibles.

Si bien ambos tipos están disponibles, es recomendable siempre usar Int.

let numero1 = 5
print(numero1)

// Imprime: 5

let numero2 = numero1 + 6
print(numero2)

// Imprime: 11

var numero3:Int32 = 90
numero3 = numero3 + 40
print(numero3)

// Imprime: 130

Números de coma flotante

Son aquellos números que poseen parte decimal, como por ejemplo: 3.14 o -45.392607. Existen dos tipos:

  • Double: se utiliza para representar un número de coma flotante de 64 bits
  • Float: se utiliza para representar un número de coma flotante de 32 bits

El hecho de que Double sea de 64 bits implica que puede almacenar un mayor rango de valores, con una precisión de al menos 15 decimales, lo que lo hace preferible en cuanto a su uso en casos generales.

String y Character

Un Character es un carácter cualquiera mientras que un String es una cadena de texto o bien, una colección de Characters.

Para poder usar un String, basta con crear una constante o variable y asignarle la cadena de texto que queramos que almacene, encerrado entre comillas dobles:

let texto1 = "Esto es un String"

En algunas implementaciones, es posible necesitar crear un String vacío inicialmente para luego asignarle un valor más adelante. Para lograr ese comportamiento, se puede utilizar una cadena vacía o usar el inicializador que trae String:

var textoVacio = ""
var textoVacio2 = String()

textoVacio = "Hola Mundo!"
textoVacio2 = "Hola Mundo!"

Asimismo, se puede modificar una cadena de texto utilizando el operador +=, siempre que la misma esté declarada como una variable:

var textoVacio = ""
textoVacio += "Hola Mundo!"
textoVacio += " cómo estás?"
print(textoVacio)

// Imprime: Hola Mundo! cómo estás?

Como dijimos anteriormente, un String puede verse también como un conjunto de Characters. Por lo tanto, si queremos concatenar un Character a un String la operatoria es un poco distinta. Si usamos la técnica anterior la misma no va a funcionar ya que un String solo puede concatenarse con otro String. Sin embargo, podemos hacer uso del método append de String para agregar el Character en cuestión. Un punto a tener en cuenta es que para usar una variable de este tipo, es necesario especificar el tipo de dato, ya que de lo contrario Swift inferirá que se trata de un String:

var textoVacio = ""
textoVacio += "Hola Mundo! cómo estás?"
let exclamacion:Character = "!"
textoVacio.append(exclamacion)
print(textoVacio)

// Imprime: Hola Mundo! cómo estás?!

En el ejemplo, se declara una constante exclamación del tipo Character y se le asigna un valor. Como se aclaró previamente, es necesario indicarle al compilador que esta constante es del tipo Character, de lo contrario inferirá que se trata de un String de un solo carácter.

Por último, Swift incorpora una técnica muy utilizada llamada Interpolación de Cadenas (String interpolation) que permite armar una cadena de texto combinando texto, números, funciones, variables y otras expresiones. Cada ítem a incluir en la cadena debe ser encerrada entre paréntesis y precedida por una barra invertida:

let cadena = "El número \(numero3) es más grande que el número \(numero2)"
print(cadena)

// Imprime: El número 130 es más grande que el número 11

Como se puede ver en el ejemplo, estamos usando las variables numero2 y numero3 creados anteriormente para armar un texto que los incorpora en un mismo String resultante. Recordemos que estas variables son del tipo Int, pero al estar dentro de los paréntesis y la barra, se convierte su tipo o salida a String y se concatena al resto de la cadena.

Por lo tanto, para concatenar string en Swift tenemos la opción de usar el operador +=, en el cual a una variable String se le puede adicionar otro a continuación, el operador +, para construir una cadena a partir de otras que se concatenan mediante ese operador, o la interpolación de cadenas, que a su vez nos permite convertir a String valores de otro tipo de dato e insertarlo dentro de una cadena resultante.

Bool

El tipo de dato booleano permite que una constante o variable pueda valer true o false. Son utilizados para tomar decisiones en base a comparaciones lógicas.

let heladeraLlena = false
if heladeraLlena {
    print("A preparar algo para comer!")
} else {
    print("Tenemos que comprar comida")
}

// Imprime: Tenemos que comprar comida

En el ejemplo anterior, se declara una constante indicando que la heladera no se encuentra llena. Luego se pregunta si la misma está llena, pero al no estarlo se ejecuta solo la sentencia encerrada en el else.

Tuplas

Las tuplas permiten agrupar varios valores de cualquier tipo en uno solo.

let ubicacion = (-34.599722, -58.381944)
print("Buenos Aires se encuentra en la longitud \(ubicacion.0)")

// Imprime: Buenos Aires se encuentra en la longitud -34.599722

En el ejemplo anterior, declaramos una tupla del tipo (Double, Double)con las coordenadas de longitud y latitud de la provincia de Buenos Aires. Luego, usamos la longitud haciendo referencia a ella por su posición en la Tupla (el cual empieza por el índice 0) y se imprime su valor dentro de un String más grande usando la técnica de String Interpolation.

Un punto a tener en cuenta es que las tuplas pueden contener cualquier cantidad de elementos y los mismos pueden ser a su vez de distintos tipos, como por ejemplo:

  • (Int, Int)
  • (Int, String)
  • (Bool, String, Int)
  • (Double, Float, Bool, String, Int)

Otra forma de acceder a los valores que contiene la tupla es mediante el nomenclado de sus elementos. De esta manera, al ponerle un nombre a cada uno, simplemente hacemos referencia a ellos por este atributo:

let registro = (nombre: "Fernando", esMayorDeEdad: true)
if registro.esMayorDeEdad {
    print("\(registro.nombre) está autorizado a ingresar al club")
}

// Imrpime: Fernando está autorizado a ingresar al club

En este caso, para poder hacer uso de los datos de la tupla registro, simplemente hacemos referencia a los nombres de sus elementos.

Las tuplas son especialmente útiles cuando se utilizan como valores de retorno en funciones, ya que les da la posibilidad de devolver más de un valor al mismo tiempo.

Tipos de colección

Existen tres tipos de colecciones en Swift: los arrays son colecciones ordenadas de elementos, los Sets son colecciones sin ordenar y con valores únicos y por ultimo, los Diccionarios son listas sin ordenar de elementos clave-valor.

Todos los tipos de colección son claros en cuanto al tipo de dato que almacenan. Esto significa que una vez declaradas las colecciones, solo se podrán agregar elementos del mismo tipo de dato de los que fueron indicados al principio. Por lo tanto, si se dispone de un array de Strings, solo se podrá agregarle Strings y no números enteros o booleanos.

Arrays

Se trata de colecciones ordenadas de elementos, los cuales pueden aparecer más de una vez dentro del array. Como es una lista ordenada, cada elemento ocupa una posición indicado por un índice que comienza con 0. Por lo tanto, el primer elemento ocupa la posición 0, el segundo la posición 1, etcétera.

Para crear un array, se puede inferir su tipo de acuerdo a los datos iniciales que se le asigna (encerrados entre corchetes) o bien, se puede crear un array vacío usando un inicializador al cual se le debe indicar el tipo de dato:

let array1 = ["uno", "dos", "tres"]
print("El primer elemento del array es \(array1.first!)")

// Imprime: El primer elemento del array es uno

var array2 = [Int]()
array2.append(1)
array2.append(4)
array2.append(9)
print("El segundo elemento del array2 es \(array2[1])")

// Imprime: El segundo elemento del array2 es 4

En el primer ejemplo, se infiere que el arreglo será un array de Strings. En cambio en el segundo se crea inicialmente un array de enteros vacío y luego se le agregan 3 datos. Un punto a tener en cuenta es que si queremos ver el primer elemento podemos usar el subíndice 0 o el método first (al cual le agregamos un signo de exclamación por tratarse de un opcional, lo que veremos más tarde). Si queremos ver el segundo elemento, debemos usar el subíndice 1, como se ve en el segundo ejemplo.

Para eliminar un elemento del array, se utiliza el método remove(at:) y se indica el subíndice:

array2.remove(at: 1)
print("El segundo elemento del array2 es \(array2[1])")

// Imprime: El segundo elemento del array2 es 9

Al eliminar el segundo elemento del array, luego su lugar es ocupado por el numero 9 que anteriormente ocupaba el tercer lugar.

Sets

Se trata de un tipo de colección de elementos que no siguen un orden y que solo aparecen una vez dentro de la colección. Es útil cuando justamente el orden no es importante o bien, cuando se requiere asegurar que el listado contenga valores únicos.

De la misma manera que los arrays, podemos crear Sets asignándoles valores al momento de declararlos por primera vez o bien, usar inicializadores para crearlos vacíos:

let coleccion = Set<Int>()
print("Tenemos \(coleccion.count) elementos")

// Imprime: Tenemos 0 elementos

var nombres:Set<String> = ["Juan", "Luis"]

Un punto a tener en cuenta es que si se quiere usar un Set inicializándolo pasándole los datos que va a contener (segundo ejemplo) es necesario indicar el tipo de dato del mismo ya que Swift no puede inferir si se trata de un Set o de un Array. En el ejemplo, se indicó el tipo de dato Set.

Para insertar un valor en la colección se utiliza el método insert(_:) y para eliminar remove(_:):

var alumnos:Set<String> = ["Juan", "Luis"]
nombres.insert("Pedro")
nombres.insert("Javier")

for dato in nombres {
    print("\(dato)")
}

// Imprime:
// Juan
// Luis
// Pedro
// Javier

nombres.remove("Pedro")

for dato in nombres {
    print("\(dato)")
}

// Imprime:
// Juan
// Luis
// Javier

En el ejemplo anterior, usamos el Set nombres creado previamente y le agregamos dos valores adicionales. Para mostrar el contenido de la colección, usamos un for para recorrerlo, en donde se crea una constante temporal llamada dato que contiene el valor de cada elemento según la iteración de la que se trate. Es decir, el for se ejecuta 4 veces y la primera vez que ingresa, la constante dato es igual a “Lionel”. La segunda equivale a “Messi”, y así sucesivamente. Finalmente eliminamos un elemento y volvemos a recorrer la colección para validar.

Diccionarios

Un diccionario permite almacenar asociaciones de pares clave – valor, en donde todas las claves de la colección son del mismo tipo y todos los valores son del mismo tipo pero puede ser distinto que el tipo de la clave. Los elementos aquí guardados no siguen un orden determinado y no pueden repetirse dos elementos con la misma clave.

Se denomina diccionario porque al igual que un diccionario convencional, los valores aquí almacenados son buscados mediante su clave. Es por esto que las claves no pueden repetirse ya que solo se admite devolver un valor al momento de realizar una búsqueda, siempre que la clave exista.

Para poder usar un Diccionario, se debe determinar entre corchetes el par clave – valor, los cuales se separan por dos puntos (:). En el caso de querer indicar el tipo de dato de cada uno de ellos, puede hacerse usando la misma convención.

var monedas: [String:String] = ["ARS":"Peso argentino", "USD": "Dolar americano", "MXN":"Peso Mexicano"]

En este caso, se indicó el tipo de dato [String:String] de manera explícita pero, al asignarle los valores al momento de declarar la variable monedas, es perfectamente válido omitir el tipo de dato ya que el mismo se puede inferir. Es decir, también pudo haberse escrito así:

var monedas = ["ARS":"Peso argentino", "USD": "Dolar americano", "MXN":"Peso Mexicano"]

Para poder acceder al valor de una determinada clave basta con indicar la misma como si se tratase de un subíndice:

let peso = monedas["ARS"]
print("\(peso!)")

//Devuelve:
//Peso argentino

Si se quiere agregar un nuevo valor, se debe usar la misma técnica vista en el ejemplo anterior pero asignándole un valor:

monedas["CRC"] = "Colones"

var colones = monedas["CRC"]
print("\(colones!)")

//Devuelve:
//Colones

Para modificar un valor cuya clave ya existe, se utiliza la misma técnica:

monedas["CRC"] = "Colones Costa Rica"

colones = monedas["CRC"]
print("\(colones!)")

//Devuelve:
//Colones Costa Rica

Ejemplo completo

let cantidadMaximaDeJugadoresPorEquipo = 11
var cantidadDeDelanteros = 2

var nombreDeUsuario: String = "Gabriel"
let cantidadDeMesesEnUnAño: Int = 12

print(nombreDeUsuario);
//Devuelve:
//Gabriel

print(nombreDeUsuario, terminator:"")
//Devuelve:
//Gabriel

print("De los \(cantidadMaximaDeJugadoresPorEquipo), \(cantidadDeDelanteros) están jugando como delanteros")
//Devuelve:
//De los 11, 2 están jugando como delanteros

//Esto es un comentario de linea

/* Esto es un comentario
 de
 varias
 lineas*/

//Números enteros
let numero1 = 5
print(numero1)
//Devuelve:
//5

let numero2 = numero1 + 6
print(numero2)
//Devuelve:
//11

var numero3:Int32 = 90
numero3 =  numero3 + 40
print(numero3)
//Devuelve:
//130

//Números de coma flotante
var numero4:Float = 3.45

//String y Characters
let texto1 = "Esto es un String"

var textoVacio = ""
var textoVacio2 = String()
textoVacio = "Hola Mundo!"
textoVacio2 = "Hola Mundo!"

textoVacio += " cómo están?"
print(textoVacio)
//Devuelve:
//Hola Mundo! cómo están?

let exclamacion:Character = "!"
textoVacio.append(exclamacion)
print(textoVacio)
//Devuelve:
//Hola Mundo! cómo están?!

let cadena = "El número \(numero3) es más grande que el número \(numero2)"
print(cadena)
//Devuelve:
//El número 130 es más grande que el número 11

//Bool
let heladeraLlena = false
if heladeraLlena {
    print("A preparar algo para comer!")
} else {
    print("Tenemos que comprar comida :(")
}
//Devuelve:
//Tenemos que comprar comida

//Tuples
let ubicacion = (-34.599722, -58.381944)
print("Buenos Aires se encuentra en la longitud \(ubicacion.0)")
//Devuelve:
//Buenos Aires se encuentra en la longitud -34.599722

let registro = (nombre: "Gabriel", esMayorDeEdad: true)
if registro.esMayorDeEdad {
    print("\(registro.nombre) está autorizado a ingresar al club")
}
//Devuelve:
//Gabriel está autorizado a ingresar al club


//Array
let arreglo1 = ["uno", "dos" ,"tres"]
print("El primer elemento del arreglo es \(arreglo1.first!)")
//Devuelve:
//El primer elemento del arreglo es uno

var arreglo2 = [Int]()
arreglo2.append(1)
arreglo2.append(4)
arreglo2.append(9)
print("El segundo elemento del arreglo2 es \(arreglo2[1])")
//Devuelve:
//El segundo elemento del arreglo2 es 4

arreglo2.remove(at: 1)
print("El segundo elemento del arreglo2 es \(arreglo2[1])")
//Devuelve:
//El segundo elemento del arreglo2 es 9

//SET
let coleccion = Set<Int>()
print("Tenemos \(coleccion.count) elementos")
//Devuelve:
// Tenemos 0 elementos

var nombres:Set<String> = ["Lionel","Messi"]

nombres.insert("Neymar")
nombres.insert("Iniesta")

for dato in nombres {
    print("\(dato)")
}
//Devuelve:
//Lionel
//Messi
//Neymar
//Iniesta

nombres.remove("Neymar")

for dato in nombres {
    print("\(dato)")
}
//Devuelve:
//Lionel
//Messi
//Iniesta

var monedas: [String:String] = ["ARS":"Peso argentino", "USD": "Dolar americano", "MXN":"Peso Mexicano"]

let peso = monedas["ARS"]
print("\(peso!)")
//Devuelve:
//Peso argentino

monedas["CRC"] = "Colones"
var colones = monedas["CRC"]
print("\(colones!)")
//Devuelve:
//Colones

monedas["CRC"] = "Colones Costa Rica"

colones = monedas["CRC"]
print("\(colones!)")
//Devuelve:
//Colones Costa Rica