Categories
Swift Development

100 Días de Swift Día 12

Estas son mis notas para el curso https://www.hackingwithswift.com/100

Entrada anterior

“I call it my billion-dollar mistake ”

Tony Hoare (talking about the invention of optionals)

«Lo llamo mi error de un millón de dólares»

Tony Hoare (hablando de la invención de los opcionales )

Optionals

Null references cuando una variable no contiene nigua valor, pueden causar varios dolores de cabeza, Swift vida con ellas usando optionals.

Comencemos con un dato sencillo como la edad de una persona, un valor que podemos no conocer, así que podemos crear una variable de tipo Int vacía que contenga ninguna valor y al cual podemos asignarle uno después.

var edad: Int? = nil

Esta variable no guarda ningún valor pero podemos asignarle uno después

edad = 24

Unwrapping optionals

Ahora imaginemos que tenemos un variable de tipo String, una String norma tienen una propiedad count pero un variable vacía no.

var nombre: String? = nil

Por tanto Swift no nos permite usar esta propiedad en este tipo de variables o por lo menos no sin antes hacerles un unwrapping

 if let unwrapped = nombre {
     print("tiene \(unwrapped.count)  letras")
 } else {
     print("No hay un nombre.")
 }

Lo norma es usar la sintaxis if let que hace el unwrapping usando una condición.

Si nombre contienen un string ejecuta el código entre corchetes si no la condición falla y ejecuta el otro código.

Unwrapping with guard

De forma alternativa al unwrapping con if let podemos usar guard let, que espera que al encontrar un nil seas tu quién termine la función, loop o condición.

La mayor diferencia con if let es que tu valor unwrapped permanece usable después del código guard

A continuación un ejemplo

 var nombre: String? = nil

 func saludo(_ nombre: String?) {
     guard let unwrapped = nombre else {
         print("¡No suministraste un nombre!")
         return
     }

     print("Hola, ¡\(unwrapped)!")
 }

 saludo(nombre)

usar guard let nos permite lidiar con los problemas al inicio de nuestro código y lo que queda es código feliz.

Force unwrapping

En ocaciones estamos seguros que un valor opcional no es nil, por ejemplo si queremos convertir un String a Int

let str = "5"
let num = Int(str)

Esto convierte num a un Int opcional pues swift no sabe que el valor que tratas dé convertir es un “5” o un “cinco” . Pero nosotros sí lo sabemos por lo que es seguro forzar el unwrapping usando !

let num = Int(str)!

Como resultado swift convertirá tu opcional en un Int regular pero cuidada si tu valor contienen un nil tu código fallara y tu app se detendrá. Así que ten cuidado de solo forzar el unwrapping cuando estes seguro de que el valor no sera nil.

Implicitly unwrapped optionals

Los opcionales implícitos son similares a los opcionales normales, pero ya están unwrapped, los Implicitly unwrapped optionals son creados al añadir un signo de admiración después del tipo de valor.

let edad: Int! = nil

Este tipo de opcionales también harán fallar tu condigo si contienen un nil así que solo deben ser usados cuando necesitas una variable y luego estas seguro que le asignaras un valor antes de usarla.

Nil coalescing

El operador Nil coalescing permite hacer el unwrapping y si el valor es nil puedes proporcionar un valor por default el operador se represan con dos signos de interrogación ?? después de nuestra variable opcional y después debemos proporcionar un valor.

//Esta función regresa un String opcional 
func nombreDeUsuario(for id: Int) -> String? {
     if id == 1 {
         return "Taylor Swift"
     } else {
         return nil
     }
 }
  
//Aqui usamos el nil coalescing operador 
let usuario = nombreDeUsuario(for: 15) ?? "Anonymoux"

En el ejemplo de arriba, se usa el nil coalescing operator si el valor que nos regresa la función no es nil ese es el que se usa, pero si es nil se usa lo que esta después de operador

Optional chaining

Swift nos proveer un atajo cuando usamos opcionales, si quieres acceder a algo como a.b.c y b es opcional, puedes usar un ? para activar el optional chaining.

Cuando el código corre swift checara si b tiene un valor, y si es nil el resto de la línea sera ignorado y swift regresara nil, pero si tienen un valor sera unwrapped y seguirá ejecutando el código.

let nombres = ["John", "Paul", "George", "Ringo"]

let beatle = nombres.first?.uppercased()

Optional try

En dias pasados vimos como manejar errores en una función, cuando se suministra un dato erróneo

Writing throwing functions
enum errorDeContraseña: Error {
    case muyObvia
}

func revisarContraseña(_ contraseña: String) throws -> Bool {
    if contraseña == "contraseña" {
        throw errorDeContraseña.muyObvia
    }

    return true
}

do {
     try revisarContraseña("contraseña")
     print("¡Buena contraseña!")
 } catch {
     print("¿En serio 😐?")
 }

Para correr una throwing function usamos do, try y catch

Hay dos alternativas para try la primera es try? que cambia la throwing function por una función que regresa un nil como resultado


if let resultado = try? revisarContraseña("contraseña") {
    print("El resultado fue  \(resultado)")
} else {
   print("¿En serio 😐?")
}

La otra alternativa es try! que solo debes usar en caso que estes seguro que la función no fallara, pues si lo hace tu código fallara y se detendrá (crash)

try! revisarContraseña("Secreto")
print("ok")

Failable initializers

Cuando vimos force unwrapping convertimos un string a un integer opcional

let str = "5"
let num = Int(str)

Esto es se conoce como un failable initializer un initializer que podría o no funcionar.

Puedes escribir los propios usando init?() en lugar de init y responder nil si las cosas no van bien.

como en el siguiente ejemplo

struct Persona {
    var id: String

    init?(id: String) {
        if id.count == 9 {
            self.id = id
        } else {
            return nil
        }
    }
}

Typecasting

Swift siempre debe saber el tipo de valores de tus variables, pero a veces tenemos mas información que swift, tomemos el siguiente ejemplo.

class Animal { }
class Pez: Animal { }

class Perro: Animal {
    func hacerRuido() {
        print("¡Guau!")
    }
}
let mascotas = [Pez(), Perro(), Pez(), Perro()]

Swift puede ver que tanto Perro como Pez heredan de Animal así que infiere que el array es de Animal

Si queremos un loop sobre mascotas y le pida a los perros que hagan ruido, swift puede revisar que cada elemento de mascotas sea o no perro y entonces llamar a hacerRuido

Paro eso usamos as? lo cual responde con un opcional, nil si no es de la clase esperada.

for mascota in mascotas {
    if let perro = mascota as? Perro {
        perro.hacerRuido()
    }
}

Resumen

Los opciones nos dejan presentar la ausencia de un n valor de una forma limpia y no ambigua

Swift no nos deja usar opcionales sin hacerles primero un unwrapping, usando if let o guard let.

Puedes forzar un unwrapping con !, pero recuerda que si tu valor es nil tu código fallara y se detendrá.

Implicitly unwrapped optionals no requieren que hagas un unwrapping pero si no te aseguras de asignarles un valor tu código fallara y se detendrá.

Puedes usar nil coalescing (??) para hacer unwrap y proveer un valor por default.

Optional chaining es usado para manipular un opcional, si el opcional resulta ser nil la linea de código es solo ignorado.

Puedes usar try? para convertir una throwing function en un valor opcional, o usar try! para detener tu código si hay un error.

Puedes usar init?() para regresar nil si recibes un dato incorrecto.

Puedes usar typecasting (as?) para convertir un tipo de valor en otro.

Referencias

https://www.hackingwithswift.com/100/12

https://www.hackingwithswift.com/100

Categories
Swift Development

100 Días de Swift Día 11

Estas son mis notas para el curso https://www.hackingwithswift.com/100

Entrada anterior

“Inside every large program, there is a small program trying to get out.”

Tony Hoare

«Dentro de todo gran programa, hay un pequeño programa tratado de salir»

Tony Hoare

Protocols

Los protocolos son una forma de describir que propiedades y métodos algo debe tener

Por ejemplo quizás queramos crear una función que acepte algo con un ID y no nos interesa precisamente que tipo de dato se este usando.

Comenzamos creando nuestro propio protocolo, en el definimos que requerirá que los tipos que se adapten al protocolo tengan una variable ID que pueda ser escrita y leída («get» and «set» )

protocol Identificable {
    var id: String { get set }
}

No podemos crear instancias de este protocolo directamente pues su objetivo es describir simplemente los que queremos.

Por ejemplo podemos usarlo en un struct

struct Usuario: Identificable {
    var id: String
}

Esta struct tendrá que tener un id tipo string si quiere conformarse al protocolo que definimos.

De la misma forma podemos crear una función

func mostrarID (loQueSea : Identificable ){
    print("El id es \(loQueSea.id)")
}

Protocol inheritance

Al igual que las clases los protocolos pueden usar herencia, pero con la diferencia de que puedes heredar de multiples protocolos al mismo tiempo y luego poner tu propio protocolo sobre todos ellos.

Comencemos creando tres protocolos nuevos.

protocol SeLePuedePagar {
    func calcularSueldo() -> Int
}

protocol NecesitaCapacitacion {
    func capacitar()
}

protocol TieneVacaciones {
    func tomarVacaciones(dias: Int)
}

Ahora lo mas genial, podemos combinar estos tres protocolos en uno solo

protocol Empleado: SeLePuedePagar, NecesitaCapacitacion, TieneVacaciones {}

Ahora en lugar de hacer que nuestros datos se conformen a cada uno de estos tres protocolos, podemos hacer que se conformen solo al protocolo Empleado

Extensions

Las extensiones hacen precisamente lo que dicen sus nombres, nos permiten añadir funciones o métodos a tipos de datos ya existentes, por ejemplo podemos añadir un método para obtener el cuadrado de un Int

extension Int {
    func alCuadrado() -> Int {
        return self * self
    }
}

var numero =  1
numero.alCuadrado()

Swift no nos permite añadir propiedades almacenadas (stored properties) a una extensión pero si propiedades calculadas (computed properties) por ejemplo

extension Int {
    var esPar: Bool {
        return self % 2 == 0
    }
}

Protocol extensions

Los protocolos nos permiten describir que métodos algo debe tener, pero no provee el código dentro.

Las extensiones nos dejan tener nuestros propio código y métodos pero lo aplican solo a un tipo de datos.

Protocol extensions resuelven ambos problemas, son similares a las extensiones nórmales pero en lugar de aplicarse a un tipo de dato como Int se extienden a todo un protocolo así que todos los datos que se conformen a ese protocolo lo obtienen.

En este ejemplo tenemos un Array y un Set con algunos nombres

let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])

Ambos tipos de datos se conforman con el protocolo Collection, así que podemos crear una extensión para ese protocolo.


let pythons = ["Eric", "Graham", "John", "Michael", "Terry", "Terry"]
let beatles = Set(["John", "Paul", "George", "Ringo"])

extension Collection {
    func recapitular() {
        print("Son \(count) de nosotros:")

        for name in self {
            print(name)
        }
    }
}

El nuevo método que hemos creado se añadió a todos los tipos de datos con ese protocolo.

pythons.recapitular()
beatles.recapitular()

Protocol-oriented programming

Protocol extensions nos permite añadir métodos por default a nuestros protocolos, eso hace mucho mas fácil que los tipos de datos se conformen a nuestros protocolos

Por ejemplo si creamos un protocolo llamado Identificable que requiere que se tenga un ID y un método llamado identificarse()

protocol Identificable {
    var id: String { get set }
    func identificarse()
}

En este caos todas los tipos de datos que quieran conformarse al protocolo Identificable necesitan implementar su propio método identificarse()

¿Pero qué tal si nosotros los proporcionamos por default con un Protocol extension?

extension Identificable {
    func identificarse() {
        print("Mi ID es \(id).")
    }
}

Ahora podemos crear una Struct que se conforme a nuestro protocolo, y que ademas de manera gratuita obtenga una implementación de identificarse()

struct Agente: Identificable {
    var id: String
}

let james = Agente(id: "007")
james.identificarse()

Resumen

Los protocolos describen que métodos y propiedades debe tener un tipo de dato que quiere conformarse a ellos. Pero no proveen la implementación de esos métodos.

Puedes construir protocolos sobre otros protocolos, igual que las clases.

Las extensiones nos permiten añadir métodos y propiedades computadas a tipos de datos ya existentes

Las extensiones de protocolos nos permiten añadir métodos y propiedades computadas a protocolos ya existentes.

Protocol-oriented programming consisten en basar la arquitectura de tu app en una serie de protocolos , y luego usar una serie de protocolos para proveer implementaciones de métodos por default.

//y ese fue el resumen de protocolos, sin duda una tema fascinante que te ayudará a reducir la cantidad de código que reescribes, ademas de proveer un nuevo tipo de paradigma de programación. 

Referencias

https://www.hackingwithswift.com/100/11

https://www.hackingwithswift.com/100

https://developer.apple.com/videos/play/wwdc2015/408/

Proximo dia

Categories
Articulo personal Swift Development

100 Días de Swift Día 10

Estas son mis notas para el curso https://www.hackingwithswift.com/100

Entrada anterior

“Any fool can write code that a computer can understand, but good programmers write code that humans can understand.”

Martin Fowler

«Cualquier tonto puede escribir código que una computadora pueda entender, pero buenos programadores pueden escribir código qué humanos pueden entender.»

Martin Fowler

Classes

Las clases son similares a las structs, te permiten nuevos tipos de datos, con propiedades y métodos.

Las clases tienen por lo menos cinco diferencias principales con las Clases

Creating your own classes

La primera es que las clases no vienen un un inicializador por default, siempre debes crearlo tú.

class Perro {
    var nombre: String
    var raza: String

    init(nombre: String, raza: String) {
        self.nombre = nombre
        self.raza = raza
    }
}

Crear instancias de una clase es igual que crear instancias de una estructura.

let cachorrito = perro(nombre: "Toby", raza: "Poodle")

Class inheritance

La segunda diferencia es que puedes crear una clase partir de otra clase ya existente, esto hereda todas las propiedades y métodos de la clase padre.

Por ejemplo creemos una clase en base a nuestra clase anterior Perro

class Sabueso: Perro {

}

Esta clase nueva hereda el inicializador y los parámetros de su clase padre

Aun así podemos crear un inicializador propio de Sabueso por ejemplo ya que sabemos que nuestro perro es un sabueso podemos iniciar nuestra clase sabueso solo con el nombre.

class Sabueso: Perro {
    
    init(nombre: String) {
        super.init(nombre: nombre, raza: "Sabueso")
    } 
}

Por razones de seguridad, swift siempre te pedirá llamar al «super.init» al crear una clase hija

let cazador = Sabueso(nombre: "Maylo")

Overriding methods

Las clases hijas pueden remplazar los métodos de sus padres con implementaciones propias.

Este proceso se conoce como «overriding»

Añadamos un método a nuestra clase Perro

class Perro {
    var nombre: String
    var raza: String

    init(nombre: String, raza: String) {
        self.nombre = nombre
        self.raza = raza
    }

 
}

Ahora podemos llamar este método desde cualquier instancia de su clase hija

cazador.ladrar()

Ahora podemos remplazar el método en nuestra clase hija para ponerle nuestra propia implementación.

Para eso usamos la palabra reservada «override» esto sire para evitar remplazar métodos por accidente.

 class Sabueso: Perro {
     
     init(nombre: String) {
         super.init(nombre: nombre, raza: "Sabueso")
     }

    override func ladrar() {
        print("¡Woolf! ¡Woolf! ¡Woolf!")
    }
 }
 
 let cazador = Sabueso(nombre: "Maylo")
cazador.ladrar()
// esto imprime "¡Woolf! ¡Woolf! ¡Woolf!"

Final classes

Sin bien la herencia es algo genial, no es algo que siempre queramos, por ejemplo si queremos marcar nuestras clases como finales y evitar que otros desarrolladores remplacen nuestros métodos solo debemos marcar la clase como final.

 final class Perro {
     var nombre: String
     var raza: String

     init(nombre: String, raza: String) {
         self.nombre = nombre
         self.raza = raza
     }
     
     func ladrar (){
         print("¡Guau! ¡Guau! ¡Guau!")
     }
 }
Cómo vemos eso impide la herencia.

Copying objects

La tercera diferencia entre clases y estructuras es lo que ocurre al copiarla

Al copiar una estructura, tanto la original como la copia son instancias diferentes, cambiar una no afecta la otra.

Cuando copias una clase, tanto la original como la copia son la misma, cambiar algo en una afecta a la otra.

let cachorrito = Perro(nombre: "Toby", raza: "Poodle")

let copiaDeCachorrito = cachorrito

copiaDeCachorrito.nombre = "Firulais"

print(cachorrito.nombre)

Al correr este código veras que al editar la copia de cachorrito también afecta al original. Esto ocurre por que ambas instancias apuntan al mismo espacio en al memoria

Deinitializers

La curta diferencia entre clases y estructuras es que las clases pueden tener «Deinitializers» código que se ejecuta cuando una instancia de la clase es destruida.

Para crear un Deinitializer usamos la palabra reservada «deinit»

class Chihuahua: Perro {
    init() {
        
        super.init(nombre: "Panchito", raza: "Chihuahua")
        print("¡Ahy ahy \(nombre) esta vivo!")
    }
    
    deinit {
        print("¡Ahy ahy 🤧😭 \(nombre) ya se murio!")
    }
}
 
for _ in 1...3 {
    let perro = Chihuahua()
}
Eso fue divertido

Mutability

La quinta y ultima diferencia entre clases y estructuras es la manera en como lidian con las constantes

En una estructura si tenemos una instancia que es una constante, y dentro de ella variables, la variable no puede ser cambiada pues toda la estructura es una constante.

Sin embargo en una clase si tenemos una instancia que es una constante y dentro tenemos una variable esta si que puede ser cambiada, por eso clases no necesitan mutating en sus métodos que cambian propiedades.

let perrito = Perro(nombre: "Toby", raza: "Callejero")
perrito.nombre = "Clifor"

print(perrito.nombre)

Si queremos evitar que esto pase debemos definir nuestra parámetro como una constante.

class Perro {
    //ahora ya no podrá ser cambiado
     let nombre: String
     var raza: String

     init(nombre: String, raza: String) {
         self.nombre = nombre
         self.raza = raza
     }
     
     func ladrar (){
         print("¡Guau! ¡Guau! ¡Guau!")
     }
  
 }

Resumen

Las clases y las estructuras son similares, ambas pueden crear tipos de datos personalizados, con sus propias propiedades y métodos

Las clases pueden heredar propiedades y métodos a otras, mediante un sistema de jerarquía llamado herencia en el cual creas clases a partir de otras

Puedes evitar la herencia marcando tu clase como final

El remplazo de métodos en una clase hija permite crear un remplazo del método de la clase padre con su propia implementación esto en POO se conoce como polimorfismo.

Cuando dos variables (o instancias) apuntan a la misma clase ambos comparten el mismo espacio de memoria, modificar una modifica a la otra.

Las clases tienen desinicializadores que es código que corre cuando una instancia de la clase es destruida.

Las clases aunque sean constantes pueden ser modificadas si alguna de sus propiedades es una variable.

//y bueno esas fueron mis notas para clases sin duda un tema super interesante y muy relacionado con POO

Referencias

https://www.hackingwithswift.com

https://www.hackingwithswift.com/100/10

Proximo día