Sobrevivir en Swift: Parte 2
Tags: ios, swift, spanish
Published: 2021-01-01 16:18:37

Esta es la parte 2 de un post en dos partes.
La parte 1 [aquí](https://dev.to/fmo91/sobrevivir-en-swift-parte-1-2c26)

# Programación orientada a objetos
En un programa tenemos dos conceptos perfectamente definidos: Datos y Comportamiento.
Una tupla con cinco campos que define una dirección es Dato. Una función que toma esa tupla y devuelve un `String` con su descripción, es Comportamiento.
 
Todo lo que hemos hecho hasta el momento en la primera parte de la introducción a Swift ha sido definir los Datos por un lado y el Comportamiento por el otro, lo cual nos ha funcionado bien. Sin embargo, la forma más frecuente de organizar el código en los lenguajes modernos, tales como Swift es integrando Datos y Comportamiento en estructuras como las que veremos hoy, cuando estudiemos **Programación orientada a objetos** (POO).
 
Esto nos dará algunas ventajas. Principalmente, la capacidad de abstraer. La clase tendrá comportamiento que se podrá utilizar desde afuera de ella. Es decir, sabremos **qué** hace la clase. Sin embargo, el **cómo** se lo reserva la clase para sí. Esto se llama **encapsulamiento**.

# Class
 
Una clase es una de las estructuras que integran datos y comportamiento en una unidad. Los datos en una clase se llaman **Atributos**. El comportamiento en una clase se modela por medio de funciones llamadas **Métodos**.
En Swift, una clase se define con la palabra clave `class` seguida por su nombre. Los atributos serán variables, y los métodos, funciones.
Veamos un ejemplo de una jerarquía de clases, y luego explicaremos en detalle qué sucede:

```swift
class Animal {
    var nombre: String
    var paseos: [String]
    
    var tipo: String { return "" }
    
    init(nombre: String) {
        self.nombre = nombre
        self.paseos = []
    }
    
    func emitirSonido() {}
    
    func pasear(a lugar: String) {
        self.paseos.append(lugar)
    }
}

class Perro: Animal {
    override var tipo: String { return "Perro" }
    override func emitirSonido() {
        print("\(nombre): Guau!")
    }
}

class Gato: Animal {
    override var tipo: String { return "Gato" }
    override func emitirSonido() {
        print("\(nombre): Miau!")
    }
}

class Persona {
    private var nombre: String
    private var mascotas: [Animal]
    
    init(nombre: String, mascotas: [Animal]) {
        self.nombre = nombre
        self.mascotas = mascotas
    }
    
    func llegarACasa() {
        print("\(nombre): llega a casa")
        for mascota in mascotas {
            mascota.emitirSonido()
        }
    }
}

let romina = Persona(
    nombre: "Romina",
    mascotas: [
        Gato(nombre: "Fausto"),
        Perro(nombre: "Lupi")
    ]
)

romina.llegarACasa()
```

Imprimirá:

``` 
Romina: llega a casa
Fausto: Miau!
Lupi: Guau!
```
 
## Herencia
 
Bien, ¿qué sucedió aquí? Primero, definimos una clase base, llamada `Animal`. Se denomina clase base porque es la clase que define la base de la jerarquía de clases. `Animal` define la funcionalidad común a todos los animales.
 
¿Qué puede hacer un animal? (comportamiento). En este caso puede salir a pasear y puede emitir un sonido. ¿Qué sonido? No lo sabemos. El sonido que hará el animal será definido en las subclases. Esto se denomina **herencia**. Un animal puede ser un `Gato` o un `Perro` en este ejemplo. Entonces un `Gato` puede decir "Miau" y definir su tipo como "Gato", pero **Hereda** todo el comportamiento definido en su clase base.

¿Qué podemos saber de un animal? (datos). En este caso, su nombre y los paseos que ha realizado.
 
¿Y `tipo`? Es una variable computada, o **getter**. Un getter es una función que devuelve un valor. Desde fuera de la clase se ve y se usa como una variable a la cual no se le puede cambiar el valor. Forma parte del comportamiento de la clase.

## Encapsulamiento

Una clase solo expone la parte de su comportamiento y sus datos que debemos utilizar desde afuera. Lo que es interno de la clase se define con la palabra clave `private`. Todo lo que sea `private` solo puede ser llamado desde dentro de la clase. Lo que no se marca como `private` es accesible desde cualquier parte de la aplicación y se denomina **interfaz pública** de la clase.

## Init
 
El `init` es un método especial de la clase que se encarga de definir cómo se creará un objeto de esa clase. En este ejemplo, la clase `Persona` tiene un `init` definido así:
 
```swift
 init(nombre: String, mascotas: [Animal]) {
     self.nombre = nombre
     self.mascotas = mascotas
 }
```
 
`self` en este caso se refiere al propio objeto en el que estamos operando.
 
El `init` no necesita ser escrito para utilizarlo. Vean el ejemplo:
 
```swift
 let romina = Persona(
     nombre: "Romina",
     mascotas: [
         Gato(nombre: "Fausto"),
         Perro(nombre: "Lupi")
     ]
 )
```
 
## Polimorfismo
 
La funcionalidad descrita en la clase base puede ser redefinida con la palabra clave `override` en las clases hijas. Por ejemplo, `emitirSonido` no tiene funcionalidad real en la clase base o clase padre. Pero en las clases hijas sí. Las clases hijas "overridean" el comportamiento de la clase padre. Por ejemplo:
 
```swift
 override func emitirSonido() {
     print("\(nombre): Guau!")
 }
```
 
Lo que es interesante es que nos podemos referir tanto a perros como a gatos en el `Array` de `mascotas`. Cuando llamamos a `mascota.emitirSonido()` no sabemos si la mascota es un perro o un gato, eso se define en tiempo de ejecución, donde cada objeto ejecuta su funcionalidad según su clase específica.

# Protocol
Una `class` responde a la pregunta **"¿Qué es?"**.
 
- ¿Qué es? Un Gato
 
- ¿Qué es? Un Perro
 
- ¿Qué es? Una Persona
 
Un `protocol` responde a la pregunta **"¿Qué puede hacer?"**. En su forma más simple, un protocolo (también llamado *interface* en otros lenguajes de programación como Java), es un conjunto de firmas de métodos bajo un nombre. Cualquier clase puede **implementar** un protocolo. Si una clase implementa un protocolo, entonces se compromete a implementar todos los métodos definidos en él. De lo contrario, el código no compilará.
 
Veamos un ejemplo

```swift
protocol EmiteSonido {
    func emitirSonido()
}

class Animal {}

class Perro: Animal, EmiteSonido {
    func emitirSonido() {
        print("Guau!")
    }
}

class Gato: Animal, EmiteSonido {
    func emitirSonido() {
        print("Miau!")
    }
}

class Timbre: EmiteSonido {
    func emitirSonido() {
        print("Ring!")
    }
}
```

Noten aquí que:
 
1. Una clase puede heredar de una sola clase. Sin embargo, puede implementar cualquier cantidad de protocolos como necesitemos. Si una clase hereda y también implementa protocolos, lo que vaya a la derecha del `:` debe ser primero la clase padre, y luego los protocolos a implementar.
2. Un Perro, un Gato y un Timbre ahora son del mismo tipo. Todos son del tipo `EmiteSonido`. Por lo tanto, podemos usarlos en un `Array`, por ejemplo.

```swift
class EfectosDeSonido {
    let sonidos: [EmiteSonido]
    
    init(sonidos: [EmiteSonido]) {
        self.sonidos = sonidos
    }
    
    func reproducir() {
        for sonido in sonidos {
            sonido.emitirSonido()
        }
    }
}

let sonidosLlegadaACasa = EfectosDeSonido(sonidos: [Timbre(), Gato(), Perro()])
sonidosLlegadaACasa.reproducir()
// Ring!
// Miau!
// Guau!
```

Al implementar `EmiteSonido`, todos los objetos, sin importar su clase, pueden servir al propósito de emitir un sonido. En este ejemplo, esto nos sirve para implementar efectos de sonido. En la práctica, este mismo principio se usa muchísimo. Sobre todo cuando lo que necesitamos de otro objeto es lo que puede hacer, sin importar qué sea en verdad.

# Struct
Una `struct` o es estructura es muy similar a una clase. Veamos algunas características:
 
Una clase:
- Tiene atributos y métodos
- Los atributos y métodos que contiene pueden ser privados
- Puede implementar protocolos.
- Debe implementar un método `init` para inicializar sus atributos al instanciarse.
- Puede heredar de otras clases.
 
Una estructura:
- Tiene atributos y métodos
- Los atributos y métodos que contiene pueden ser privados
- Puede implementar protocolos.
- **NO ES NECESARIO** que implemente un método `init` para inicializar sus atributos por lo general.
- **NO PUEDE HEREDAR** de ninguna clase o estructura.
 
Una clase tampoco puede heredar de una estructura.

```swift
protocol Describible {
    func obtenerDescripcion() -> String
}

struct Direccion: Describible {
    let calle: String
    let numero: String
    let departamento: String
    let piso: String
    let ciudad: Ciudad
    
    func obtenerDescripcion() -> String {
        return "\(calle) \(numero) - Dto. \(piso)-\(departamento) - \(ciudad.obtenerDescripcion())"
    }
}

struct Ciudad: Describible {
    let nombre: String
    let provincia: Provincia
    
    func obtenerDescripcion() -> String {
        return "\(nombre), \(provincia.obtenerDescripcion())"
    }
}

struct Provincia: Describible {
    let nombre: String
    let pais: String
    
    func obtenerDescripcion() -> String {
        return "\(nombre), \(pais)"
    }
}

let direccionOficina = Direccion(
    calle: "Avenida Rivadavia",
    numero: "18451",
    departamento: "1",
    piso: "PB",
    ciudad: Ciudad(
        nombre: "Morón",
        provincia: Provincia(
            nombre: "Buenos Aires",
            pais: "Argentina"
        )
    )
)

print(direccionOficina.obtenerDescripcion()) // Avenida Rivadavia 18451 - Dto. PB-1 - Morón, Buenos Aires, Argentina
```

En la práctica, las `struct` se utilizan para diseñar trozos pequeños de información. Pueden servirnos para describir una dirección, los datos de una tarjeta de crédito, etc.

# Enum
Una `struct` o una `class` nos permiten diseñar a partir del "y". Por ejemplo, una `struct Persona` tiene `nombre` y `edad` y `email` como sus miembros.
Una `enum`, en cambio, nos permite diseñar a partir del "o". Por ejemplo, una `enum Provincia` tiene `buenosAires` o `entreRios` o `catamarca` como sus miembros.
 
Al contrario de las `enum` en lenguajes como C, las `enum` de Swift pueden llegar a resultar **MUY** complejas. De todos modos, aquí usaremos las funcionalidades básicas.
 
Veamos un ejemplo simple:

```swift
class Usuario {
    var nombre: String
    var tipoCredenciales: TipoCredenciales
    
    init(
        nombre: String,
        tipoCredenciales: TipoCredenciales
    ) {
        self.nombre = nombre
        self.tipoCredenciales = tipoCredenciales
    }
}

// La enum TipoCredenciales define con qué medio el usuario creó su cuenta.
enum TipoCredenciales {
    case email
    case facebook
    case apple
    case google
}
```

Con `TipoCredenciales` podemos decir por ejemplo que un usuario se registró por `email`, por `facebook`, por `apple` o por `google`. Es imposible que un usuario se haya registrado por más de una vía (al menos en el contexto de este dominio ficticio),  y el mismo código lo hace imposible.
 
## Switch en enum
Bien, intentemos ahora utilizar `TipoCredenciales` para nuestra lógica. La forma más típica de utilizar una `enum` en una lógica, por ejemplo dentro de una función, es mediante la cláusula `switch`.

```swift
func esUsuarioDeRedesSociales(_ usuario: Usuario) -> Bool {
    switch usuario.tipoCredenciales {
    case .email:
        return false
    case .facebook, .apple, .google:
        return true
    }
}
```

Nótese que también podríamos haber escrito esta función como un método dentro del usuario. También, que aquí combinamos varias opciones dentro de un mismo `case`, pero que bien podríamos haber escrito un `case` por cada uno de los métodos (facebook, apple, google).
 
## Métodos y atributos en enum
Una `enum` puede tener métodos y atributos, tal como las clases. El patrón más común para ello es incluir un `switch self` dentro del mismo. Veamos un ejemplo:

```swift
enum Pais {
    case argentina, alemania, inglaterra, mexico, camerun
    
    // Crearemos una propiedad computada. Esto es un `getter`.
    // Noten que esto es lo mismo que decir
    //
    // func esEuropeo() -> Bool { ... }
    //
    // Salvo que al llamarse no usaremos paréntesis, tal y como si se tratase de una
    // propiedad común y corriente del objeto.
    var esEuropeo: Bool {
        // switch self es MUY común en estos casos.
        switch self {
        // Si estamos llamando esta propiedad computada desde los cases 'alemania' y 'inglaterra'
        // entonces devolveremos true
        case .alemania, .inglaterra:
            return true
        // En cambio, si lo hacemos desde cualquier otro case de la enum, entonces
        // devolveremos false.
        default:
            return false
        }
    }
    
    // Similar al caso anterior pero con un String.
    var nombre: String {
        switch self {
        case .argentina:
            return "Argentina"
        case .alemania:
            return "Alemania"
        case .inglaterra:
            return "Inglaterra"
        case .mexico:
            return "México"
        case .camerun:
            return "Camerún"
        }
    }
}

func describir(pais: Pais) {
    if pais.esEuropeo {
        print("\(pais.nombre) es europeo")
    } else {
        print("\(pais.nombre) NO es europeo")
    }
}

describir(pais: Pais.alemania) // Alemania es europeo
```

Otra curiosidad con respecto a las `enum` es que cuando requerimos el uso de una, como en este caso en la función `describir`, no es necesario especificar el tipo. Por ejemplo, `Pais.alemania`, podría haber sido tranquilamente solo `.alemania`.

```swift
describir(pais: .argentina) // Es igual que describir(pais: Pais.argentina), y se ve más natural.
```

## rawValue
Existe una forma alternativa de definir el nombre del país, como vimos en el ejemplo anterior, y es por medio de un `rawValue`. Cada `enum` puede tener un único `rawValue` para cada uno de sus `case`, y todos deben ser del mismo tipo.
 
El `rawValue` no nos permite únicamente obtener un valor asociado a cada uno de los `case`, sino también construir un `case` de la `enum` a partir de ese valor asociado.
 
Veamos un ejemplo:

```swift
enum Provincia: String {
    case cordoba = "Córdoba"
    case buenosAires = "Buenos Aires"
    case santaFe = "Santa Fe"
}

let provincia1 = Provincia.cordoba
print("La provincia 1 es \(provincia1.rawValue)")
// Usamos el rawValue para obtener el valor de la provincia
// esto devolverá "La provincia 1 es Córdoba"

let provincia2 = Provincia(rawValue: "Buenos Aires")
// Aquí estamos haciendo el proceso inverso.
// En vez de obtener el rawValue de una Provincia,
// creamos la provincia a partir de un rawValue.
//
// Como podemos confundirnos, no hay garantía de que exista
// una provincia con ese nombre, y el tipo de provincia2
// es `Provincia?`, una Provincia opcional.

if let provincia = provincia2 {
    print("Pudimos obtener la provincia2 y es \(provincia.rawValue)")
} else {
    print("No pudimos obtener la provincia2, y es nil")
}
```

## Valores asociados
Este es el último caso que veremos aquí sobre las `enum`. Lo quiero remarcar porque es algo muy utilizado en Swift, aunque no entra dentro del scope del curso. Quiero decir, no es necesario saberlo para poder desarrollar la aplicación que haremos aquí. Sin embargo, saber esto puede llegar a resultar muy útil.
 
Cada caso de una enum puede llegar a tener valores asociados que difieren unos de otros. Por ejemplo, imaginemos un paquete de galletitas surtidas. Las galletitas podrían estar representadas por una `enum`. Puedo estar confundiéndome entre galletitas, fanboys/fangirls de galletitas surtidas por favor tener paciencia.

```swift
enum Galletita {
    // Cada uno de los case puede tener cualquier cantidad de valores asociados. Cada uno puede opcionalmente
    // tener un nombre asociado, y puede ser del tipo que se desee.
    case sonrisas(saborDeMasa: SaborDeMasa)
    case rellena(relleno: Relleno, saborDeMasa: SaborDeMasa)
    case bocaDeDama
    
    var descripcion: String {
        switch self {
        // Al momento de realizar un switch sobre la enum, podemos
        // obtener los valores asociados por medio de `let`.
        case .sonrisas(let saborDeMasa):
            return "Sonrisa de \(saborDeMasa.descripcion)"
        case .rellena(let relleno, let saborDeMasa):
            return "Galletita rellena de \(relleno.descripcion), sabor \(saborDeMasa.descripcion)"
        case .bocaDeDama:
            return "Boca de dama"
        }
    }
}

enum Relleno {
    case vainilla, frambuesa
    
    var descripcion: String {
        switch self {
        case .vainilla: return "vainilla"
        case .frambuesa: return "frambuesa"
        }
    }
}

enum SaborDeMasa {
    case vainilla, chocolate
    
    var descripcion: String {
        switch self {
        case .vainilla: return "vainilla"
        case .chocolate: return "chocolate"
        }
    }
}

struct PaqueteDeGalletitas {
    let galletitas: [Galletita]
    
    func describir() {
        print("-")
        print("Paquete:")
        for galletita in galletitas {
            print(galletita.descripcion)
        }
    }
}

let surtido = PaqueteDeGalletitas(galletitas: [
    .bocaDeDama,
    .rellena(relleno: .frambuesa, saborDeMasa: .chocolate),
    .rellena(relleno: .frambuesa, saborDeMasa: .chocolate),
    .rellena(relleno: .frambuesa, saborDeMasa: .chocolate),
    .sonrisas(saborDeMasa: .chocolate),
    .sonrisas(saborDeMasa: .vainilla),
    .sonrisas(saborDeMasa: .vainilla),
    .bocaDeDama,
    .bocaDeDama,
    .sonrisas(saborDeMasa: .chocolate)
])

surtido.describir()
//    -
//    Paquete:
//    Boca de dama
//    Galletita rellena de frambuesa, sabor chocolate
//    Galletita rellena de frambuesa, sabor chocolate
//    Galletita rellena de frambuesa, sabor chocolate
//    Sonrisa de chocolate
//    Sonrisa de vainilla
//    Sonrisa de vainilla
//    Boca de dama
//    Boca de dama
//    Sonrisa de chocolate
```

# Closures
 
Los `closure` o clausuras son también llamadas funciones anónimas. Veamos el concepto primero de función como tipo de dato.
 
## Funciones como tipo de dato
 
Las funciones son un tipo de dato, como `Int`, `Double`, `Bool`, una `class`, `struct` o `enum`. Esto implica que uno podría tomar una función y enviarla como argumento a otra función, o hacer que una función devuelva otra función como resultado, o tener una `struct` donde uno de sus atributos sea una función. Esto es en realidad algo extraño si venimos de un lenguaje como Java, C o C++, pero es en realidad bastante común en otros lenguajes.
 
Para convertir una función en su tipo de dato correspondiente, debemos ver los tipos de los argumentos que recibe, y el tipo de dato que devuelve. Así, una función sumar:
 
```swift
func sumar(x: Int, y: Int) -> Int { ... }
```
 
Pasa a ser de tipo `(Int, Int) -> Int`, porque recibe dos enteros y devuelve un entero. Veamos otros ejemplos:
 
```swift
func sumar(x: Int, y: Int) -> Int { ... } // (Int, Int) -> Int
func describir(a persona: Persona) { ... } // (Persona) -> Void
func imprimirHoraActual() { ... } // () -> Void
func obtenerFechaActual() -> String { ... } // () -> String
```
 
Y, como dijimos, un tipo de dato función puede usarse como argumento para otras funciones:

```swift
struct Persona {
    let id: Int
    let nombre: String
    let ocupacion: Ocupacion?
    let edad: Int
}

enum Ocupacion {
    case desarrollador, projectManager, contador, abogado
}

func imprimirPersonasMayoresDeEdad(_ personas: [Persona]) {
    for persona in personas {
        if persona.edad >= 18 {
            print("\(persona.id) - \(persona.nombre)")
        }
    }
}

let personas = [
    Persona(id: 1, nombre: "Franco", ocupacion: .abogado, edad: 34),
    Persona(id: 2, nombre: "Gimena", ocupacion: .projectManager, edad: 24),
    Persona(id: 3, nombre: "Gonzalo", ocupacion: .abogado, edad: 26),
    Persona(id: 4, nombre: "Noelia", ocupacion: .desarrollador, edad: 29),
    Persona(id: 5, nombre: "Pablo", ocupacion: nil, edad: 15),
    Persona(id: 6, nombre: "Lourdes", ocupacion: .contador, edad: 29),
]

imprimirPersonasMayoresDeEdad(personas)
// Esto funcionará correctamente
//
// Sin embargo, no tenemos forma de dar 'flexibilidad' al algoritmo. Quiero decir,
// dentro de la función imprimirPersonasMayoresDeEdad filtramos por edad, y luego imprimimos.
// Si quisiéramos variar la forma de filtrar, deberíamos escribir una función completamente nueva.
// Convirtamos esa función en algo más flexible:

// Estamos "inyectando" una función dentro de otra función como argumento
func imprimir(_ personas: [Persona], si cumpleCondicion: (Persona) -> Bool) {
    for persona in personas {
        if cumpleCondicion(persona) {
            print("\(persona.id) - \(persona.nombre)")
        }
    }
}

func esMayor(_ persona: Persona) -> Bool {
    return persona.edad >= 18
}

imprimir(personas, si: esMayor) // Exactamente el mismo resultado. Pasamos la función como argumento en este caso.
```

## Funciones anónimas
Ahora sí, ya con esta introducción, podemos hablar de funciones anónimas. Una función anónima o **closure** es una función que no tiene nombre. Tan simple como suena. Y el mejor contexto para usar funciones anónimas es para pasarlas a otras funciones. Por ejemplo, en este caso yo podría haber decidido que no tenía sentido definir una función solo para determinar si una persona es mayor.

Definámosla como función anónima:

```swift
print("Imprimiendo personas mayores de edad por closure (1):")
imprimir(
    personas,
    si: { (persona: Persona) -> Bool in
        return persona.edad >= 18
    }
)
```

Eso es una closure. La sintaxis puede resultar extraña y hay muchas formas de definirlas, aquí hay más ejemplos: https://fuckingclosuresyntax.com/
 
Por lo pronto, veamos la transformación paso a paso
 
Tenemos esta función:
 
```swift
 func esMayor(_ persona: Persona) -> Bool {
     return persona.edad >= 18
 }
```
 
Paso 1: Le quitamos el `func` y el nombre:
 
```swift
 (_ persona: Persona) -> Bool {
     return persona.edad >= 18
 }
```
 
Paso 2: En caso de que sus parámetros tengan nombre interno y externo, nos quedaremos únicamente con el interno:
 
```swift
 (persona: Persona) -> Bool {
     return persona.edad >= 18
 }
```
 
Paso 3: Pasamos la llave de inicio del cuerpo, al comienzo de la declaración , y en su lugar pondremos `in`:
 
```swift
 { (persona: Persona) -> Bool in
     return persona.edad >= 18
 }
```
 
¡Perfecto! Ya con esto tendremos una closure definida correctamente. Podemos quedarnos aquí, pero voy a comentarles algunas otras formas de seguir acortando esa declaración. Repito, es completamente opcional:
 
Paso opcional 1: Quitamos el tipo del argumento, el compilador puede inferirlo en la mayoría de las situaciones.
 
```swift
 { (persona) in
     return persona.edad >= 18
 }
```
 
Paso opcional 2: Quitamos los paréntesis que envuelven a los argumentos:
 
```swift
 { persona in
     return persona.edad >= 18
 }
```
 
Paso opcional 3: Si la closure tiene una única sentencia, podemos simplemente obviar el `return`.
 
```swift
 { persona in persona.edad >= 18 }
```
 
Paso opcional 4: En vez de usar los nombres de los argumentos (en este caso `persona`), podemos referirnos a los argumentos por orden de los mismos. Por ejemplo, en este caso `persona` es `$0`, si tuviéramos dos argumentos, el primero sería `$0` y el segundo `$1`. Si tuviéramos cuatro, serían `$0`, `$1`, `$2`, `$3`, y así sucesivamente:
 
```swift
 { $0.edad >= 18 }
```
 
Y más que eso no podemos acortarlo
 
```swift
print("Mayores de edad que son abogados")
imprimir(personas, si: { $0.edad >= 18 && $0.ocupacion == .abogado })
```

Si tenemos la closure como último argumento de la función, entonces podemos sacarla por fuera de la llamada a la función. Quedará más claro con un ejemplo:

```swift
print("Mayores de edad que son abogados (2)")
imprimir(personas) { $0.edad >= 18 && $0.ocupacion == .abogado } // Exactamente igual al ejemplo anterior.
```

## Map, filter, sorted y forEach
Hay funciones dentro de la biblioteca de funciones estándar que provee Swift que nos permiten pasar funciones a otras funciones, especialmente en el caso de tratamiento de `Array`.
 
- `map` es una función que nos permite transformar cada elemento del array en otro elemento pasándole una función que realiza la transformación para cada elemento.
- `filter` es una función que nos permite filtrar un array pasándole una función que devuelve `true` en caso de que el elemento tenga que ir en el `Array` de detino, o `false` en caso contrario
- `sorted` es una función que nos permite ordenar un array. Funciona de forma similar al `filter`, donde devolvemos `true` en caso de que un elemento deba ir primero que otro en el array de destino. La función de `sort` recibe dos argumentos para compararlos, y no uno solo.
- `forEach`, por último nos permite iterar sobre un array, realizando algo en el array de origen. Esta función no devuelve nada, al contrario de `map`, `filter` y `sorted`.
 
Es importante destacar que cada una de estas funciones (salvo `forEach`) devuelven un array completamente nuevo. No realizan modificaciones sobre el mismo array.

```swift
let personasMayores = personas.filter { $0.edad >= 18 }
let nombres = personas.map { $0.nombre }
let personasOrdenadasPorEdad = personas.sorted { $0.edad < $1.edad }

// También podemos "encadenar" estas funciones, ya que cada una devolverá un `Array` nuevo.

print("Funciones encadenadas:")

personas
    .filter { $0.edad >= 18 } // Solo consideramos las personas mayores de edad
    .sorted { $0.edad < $1.edad } // Luego las ordenaremos por edad
    .map { $0.nombre } // Y solo nos importará el nombre
    .forEach { print($0) } // Finalmente, imprimiremos sus nombres
// Gimena
// Gonzalo
// Noelia
// Lourdes
// Franco
```

# Extensions
Las extensions nos permiten *extender* un tipo existente para agregarle funcionalidades nuevas. Estas funcionalidades pueden ser propiedades computadas o métodos.
 
Veamos un ejemplo sencillo:

```swift
struct Direccion {
    let calle: String
    let numero: String
    let ciudad: String
}

extension Direccion {
    var descripcion: String {
        return "\(calle) \(numero) - \(ciudad)"
    }
}

let direccion = Direccion(calle: "Rivadavia", numero: "18451", ciudad: "Morón")
print(direccion.descripcion) // Rivadavia 18451 - Morón
```

Lo de recién es exactamente igual a esto:
 
```swift
 struct Direccion {
     let calle: String
     let numero: String
     let ciudad: String
 
     var descripcion: String {
         return "\(calle) \(numero) - \(ciudad)"
     }
 }
```
 
Una `extension` solo brinda la posibilidad de extender la funcionalidad de un tipo, sea `class`, `struct`, `enum`, etc.
 
Una funcionalidad interesante de las `extension` es que podemos extender también tipos nativos, tales como `Int`, `Double`, o `String`.

```swift
extension Int {
    func esMayor(que otroNumero: Int) -> Bool {
        return self > otroNumero
    }
}

if 10.esMayor(que: 5) {
    print("10 es mayor que 5")
}
```

# Typealias
 
Los `typealias` son justamente alias para tipos. Esto quiere decir que podemos referirnos a un tipo con otro nombre. Por ejemplo, imaginemos que tenemos una aplicación con usuarios, donde cada usuario tiene un identificador. Este ID del usuario es un entero. Sin embargo, un `typealias` puede traer mayor claridad cuando necesitemos referirnos al id del usuario.
 
Recordemos que un buen código es fácil de extender, y fácil de entender.

```swift
typealias IDUsuario = Int

struct Usuario {
    let id: IDUsuario
    let name: String
}

typealias IDInmueble = Int
typealias Direccion = (calle: String, numero: String, ciudad: String, provincia: String, pais: String)

struct Inmueble {
    let id: IDInmueble
    let idPropietario: IDUsuario
    let direccion: Direccion
}

let oficina = Inmueble(
    id: 1,
    idPropietario: 10,
    direccion: (
        calle: "Rivadavia",
        numero: "18451 PB Torre 2",
        ciudad: "Moron",
        provincia: "Buenos Aires",
        pais: "Argentina"
    )
)
```

Noten que podríamos haber hecho exactamente lo mismo sin `typealias`. Por lo general (salvo en casos mucho más avanzados que no veremos en este curso), los `typealias` brindan claridad al código, haciendo más evidente nuestra intención al diseñarlo.

# Ejercicios Segunda Parte
Queremos desarrollar una aplicación de viajes. La idea de la aplicación es tener un listado de posibles destinos. El usuario puede elegir sus destinos favoritos, los que podrá ver en otra lista.
Este ejercicio consiste en desarrollar las estructuras de datos necesarias para dar soporte a la aplicación. Se requiere, al menos:
 
- Usar clases, structs o enums para las entidades `User`, `Address`, `Place` (ciudades o destinos turísticos), `Landmark` (monumentos, atracciones turísticas, etc. dentro de las ciudades)
- Escribir todas las entidades, atributos y métodos en inglés.
- Permitir obtener los `Place` y `Landmark` favoritos de un usuario determinado.
 
Usar la imaginación y creatividad, y realizar el ejercicio más completo posible.