Swift – Control de Flujo

El control de flujo de un aplicación es más que fundamental, es la propia aplicación en sí. Sin un dominio de las sentencias condicionales o bucles pues básicamente no podremos hacer nada.

Swift proporciona una variedad de instrucciones para controlar el flujo de una aplicación. Dentro de estas se incluyen bucles while para la ejecución repetitiva de código; instrucciones if, guard y switch para ejecutar diferentes ramas de código basadas en ciertas condiciones; y declaraciones como break y continue que transfieren el flujo de ejecución a otro punto del código.

Condicionales

Para validar las opciones que establece el usuario, responder ante acciones que pueden termina en un error o de manera satisfactoria, para todo esto existen las expresiones condicionales en Swift. Veamos en detalle una a una.

if else

La sentencia if-else nos permite tomar desiciones ante cierta condición lógica, su sintaxis es bien sencilla:

let minimumAgeAdmitted = 17

var clientAge = 22

if clientAge > minimumAgeAdmitted {
    
    print("The client can watch the film!")
    
}

En este ejemplo nos hubicamos en un cine, tenemos una constante donde se establece la edad mínima para visualizar la película, luego tenemos una variable donde establecemos la edad del cliente que estamos atendiendo en el momento, un dato que obtenemos de la fecha en su DNI / cédula.

Luego con estos datos hacemos una simple validación en el bloque if, y que se lee de la siguiente forma: – si la edad del cliente es mayor al mínimo de edad permitida ejecuta las instrucciones dentro del bloque – en este caso la única instrucción es una función print que muestra un mensaje en pantalla, en un entorno real aquí pudiéramos pasar a escoger el asiento del cliente, procesar el pago, etc.

Ahora, ¿qué sucede si la edad es menor a 17 años? Pues aquí es donde nos valemos de else, para capturar la opción contraria al análisis que estamos haciendo.

let minimumAgeAdmitted = 17

var clientAge = 22

if clientAge > minimumAgeAdmitted {
    
    print("The client can watch the film!")
    
} else {
    
    print("The client can't watch the film!")
    
}

Como puedes constatar la sintaxis es sumamente simple, como la expresión de control la lleva el if la sentencia else se limita a establecer el código que se ejecutará si el análisis lógico devuelve false, es decir si no se cumple la condición lógica, en este caso que la edad del cliente no sea mayor a 17 años,

else if

Ahora, ¿cómo gestionamos un caso donde tengamos varias condicionales, digamos un semáforo, donde tenemos tres luces y en dependencia de cada una ejecutamos una acción?

Pudiéramos utilizar tres bloques if con sus respectivos else:

let lightRed = "Red"
let lightYellow = "Yellow"
let lightGreen = "Green"

let isCarMoving = true
let currentLight = lightGreen

if currentLight == lightRed {
    
    print("Red: Stop")
    
    if isCarMoving == true {
        
        print("Red Light Ignored: You have committed a traffic infraction.")
        
    }

}

if currentLight == lightYellow {
    
    print("Yellow: Slow down and continue with caution.")
    
    if isCarMoving == false {
        
        print("Yellow Light Ignored: You have committed a traffic infraction.")
        
    }

}

if currentLight == lightGreen {
    
    print("Green: Continue")
    
    if isCarMoving == false {
        
        print("Green Light Ignored: You have committed a traffic infraction.")
        
    }
    
}

Esta sería una opción pero está bien lejos de ser óptima. El problema reside en que una vez que se analiza la luz actual (currentLight), digamos que está en rojo y nos detenemos, ahí debería de finalizar el análisis, sin embargo no es así.

Luego de ejecutado este primer if se pasa al siguiente a verificar si la luz es amarilla, y luego a la verde. ¿Qué sentido tiene esto cuando ya validamos que está en rojo y nos hemos detenido?

Este sin dudas es un muy mal enfoque, en el cual violamos algo que debe de ser vital para todo desarrollador: estamos haciendo un uso indiscriminado de los recursos del dispositivo, estamos procesando / validando más datos de los que tendríamos, nuestro código está bien lejos de ser óptimo.

Hay más de una manera de afrontar esto mediante otras sentencias de control que veremos más adelante dentro de este mismo artículo, pero como estamos hablando de if nos apoyaremos en else if, que sería lo mismo que anidar un if con otro.

La solución es así de sencilla:

let lightRed = "Red"
let lightYellow = "Yellow"
let lightGreen = "Green"

let isCarMoving = true
let currentLight = lightGreen

if currentLight == lightRed {
    
    print("Red: Stop")
    
    if isCarMoving == true {
        
        print("Red Light Ignored: You have committed a traffic infraction.")
        
    }

} else if currentLight == lightYellow {
    
    print("Yellow: Slow down and continue with caution.")
    
    if isCarMoving == false {
        
        print("Yellow Light Ignored: You have committed a traffic infraction.")
        
    }

} else if currentLight == lightGreen {
    
    print("Green: Continue")
    
    if isCarMoving == false {
        
        print("Green Light Ignored: You have committed a traffic infraction.")
        
    }
    
}

Lo único que hemos hecho es capturar que la validación del primer if es false, es decir que la luz no está en rojo, esto lo hacemos mediante else como ya habíamos aprendido y aquí concatenamos este else con otro if donde pasamos a validar otro color.

¿Qué sucedería si el semáforo estuviese en rojo?

Pues que el primer bloque if se ejecutaría y el resto de bloques else if se ignorarían. Ya no estariamos validando el resto de colores luego de conocer que el semáforo está en rojo. Verificaríamos solo una vez sí está en rojo, dos si está en amarillo y tres en caso de ser verde. Recordemos que en la versión anterior del código verificabamos tres ves en cualquiera de las opciones.

Switch

La sentencia switch es para muchos muy similar a if-else y de hecho no está del todo claro cuando usar uno u otro. La realidad es que no tiene nada que ver.

Para aclarar las diferencias veamos los ejemplos anteriores pero esta vez utilizando switch. Comencemos con el ejemplo del cine:

let minimumAgeAdmitted = 17

var clientAge = 22

switch minimumAgeAdmitted {
    
case 12 where clientAge > 12:
    
    print("The client can watch the film!")
    
    break
    
case 17 where clientAge > 17:
    
    print("The client can watch the film!")
    
    break
    
case 0:
    
    print("There is no age limit for this film.")
    
default:
    
    print("The client can't watch the film!")

}

La sintaxis de la sentencia switch es un poco más compleja, pero nada difícil de entender. Veamos su forma básica:

switch value {

case someValue:

  // Código a ejecutar ante x valor

case otherValue:

  // Código a ejecutar ante x valor

case anotherValue where <expresión lógica>:

  // Código a ejecutar ante x valor y expresión lógica

default:

  // Código a ejecutar cuando fallan los valores anteriores

} // switch

De arriba hacia abajo: value sería el valor que estaríamos buscando a través de los tres casos que estamos evaluando (someValue, otherValue y anotherValue), donde ante el evento de una coincidencia con value el código asociado a este caso se ejecutaría.

En los casos donde utilizamos where es un poco más compleja ya que se tienen que cumplir dos condiciones: que el valor coincida y que la expresión lógica sea valida.

Por último tenemos default el cual forma parte de la sentencia switch y viene a ejecutarse siempre y cuando no se han encontrado coincidencias, sería como un else global si lo comparamos con la sentencia if.

Veamos ahora el ejemplo del semáforo:

let lightRed = "Red"
let lightYellow = "Yellow"
let lightGreen = "Green"

let currentLight = lightRed

let isCarMoving = true

switch currentLight {
    
case lightRed:
    
    print("Red: Stop")
    
    if isCarMoving == true {
        
        print("Red Light Ignored: You have committed a traffic infraction.")
        
    }
    
case lightYellow:
   
    print("Yellow: Slow down and continue with caution.")
    
    if isCarMoving == false {
        
        print("Yellow Light Ignored: You have committed a traffic infraction.")
        
    }
    
case lightGreen:
    
    print("Green: Continue")
    
    if isCarMoving == false {
        
        print("Green Light Ignored: You have committed a traffic infraction.")
        
    }
    
default:
    
    print("Broken traffic light!")
    
}

En este último ejemplo reside la razón de ser y la diferencia más notable entre la sentencia switch y un bloque if-else, y es que como podemos observar hemos complementado las funcionalidad de switch con una sentencia if.

Utilizaremos un bloque switch siempre y cuando el dato que establecemos com fuente, en «switch valor», tenga estados definidos, como un semáforo (rojo, amarillo y verde), los códigos de error en una petición HTTP (204, 404 o 500), los estados de un proyecto (diseño, desarrollo, testing y deployment), el checklist que utilizan los pilotos a la hora de despegar o aterrizar, etc.

Mientras que if-else nos viene a ayudar con validaciones más simples, más concretas y que no necesariamente están asociadas con un modelo de datos y sus estados. Tal y como se ve en el último ejemplo donde nos apoyamos en la sentencia switch para una validación muy puntual, en este caso verificar que el automovil está en movimiento o no.

Rangos

Hasta ahora hemos visto casos donde se evalúa un solo valor. Pero switch también puede evaluar valores separados por coma al igual que rangos, haciendo uso del operador de rango. Lo mejor de todo es que todos estos modos pueden coexistir en un mismo bloque switch, veamos un ejemplo:

var statusCode: Int = 404

var errorString: String = "Request failed:"

switch statusCode {

case 100, 101:

    errorString += " Informational, 1xx."

case 204:

    errorString += " Successfull but no content, 204."

case 300...307:

    errorString += " Redirection, 3xx."

case 400...417:

    errorString += " Client error, 4xx."

case 500...505:

    errorString += " Server error, 5xx."

default:

    errorString = "Unknown. Please review the request and try again."

} // switch

print(errorString)

La salida en pantalla de este código sería:

Request failed: Client error, 4xx.

Aquí nos encontramos ante un nuevo ejemplo donde introducimos algunos matices del bloque switch. Primeramente con varios valores separados por coma en un mismo caso y luego varios casos donde operamos sobre varios rangos, mediante el operador de rango.

La implementación de estos rangos creo que es más que evidente, nos ayuda a segmentar los posibles valores, de lo contrario tendriamos que establecer un caso para cada valor, algo completamente de inviable, engorroso y visualmente feo.

Default

Como ya os comenté switch va muy bien con datos que por su naturaleza establecen estados o grupos de valores relacionados. Estos datos usualmente se agrupan en enums, que es una forma de encapsular los valores que un tipo de dato puede adoptar. Algo así:

enum PasswordError: Error {
    
    case short
    case obvious
    case simple

}

Imaginemos que este enum de nombre PasswordError es parte de algun framework que estamos usando para aplicar ciertas politicas de seguridad sobre los passwords. En este enum se declaran tres tipos de error que puede generar un password. Ahora veamos como lo podemos usar con un bloque switch:

enum PasswordError: Error {
    
    case short
    case obvious
    case simple

}

let currentPasswordError = PasswordError.short

switch currentPasswordError {
    
case .short:
    
    print("Your password was too short.")
    
case .obvious:
    
    print("Your password was too obvious.")

default:
    
    print("The password does not comply with the security policies.")
    
}

En este ejemplo vemos que estamos usando la sección default dentro de switch. Esto se debe a que cuando trabajamos sobre un enum tenemos o bien que cubrir todos los casos o hacer uso de algunos y procesar el resto a través de la sección default.

En nuestro caso solo estamos utlizando los casos short y obvious ya que son las politicas que ahorita nos interesan aplicar, el resto que nos provee el framework no nos interesan de momento. Así que en caso de que currentPasswordError se establesca a un valor fuera de estos dos el bloque default devolverá el mensaje genérico que hemos establecido.

Bucles

Tanto para la ejecución condicional y repetitiva de cierto bloque de código como para el procesamiento iterativo sobre los datos que un usuario ha solicitado, para todo esto existen los bucles.

for in

El bucle for in es ideal cuando necesitamos iterar sobre una cantidad específica de elementos, cuando el número de iteraciones es tanto conocido o fácil de derivar.

Comencemos por ver un ejemplo bien sencillo:

let names = ["Gise", "Nery", "Olga", "Orlando"]

for name in names {
    
    print("Hola, \(name)!")

}

En este ejemplo contamos con la constante names igualada a un arreglo de cuatro nombres. Luego en la siguiente línea tenemos nuestro bucle for in, el cual declara la constante name (en singular) en cada iteración con el valor resultante del arreglo, es decir que en la primera iteración será igual a «Gise», la segunda vez será igual a «Nery» y así.

El ambito de la constante name se encuentra limitado al area comprendida dentro de las llaves.

La salida en pantalla sería:

Hola, Gise!
Hola, Nery!
Hola, Olga!
Hola, Orlando!

La sintaxis sería:

for <constante> in <fuente de datos> {

    // Código a ejecutar

}

Un bucle for int iterará tantas veces como elementos existan en la sección «fuente de datos» y en cada iteración la «constante» será igual al valor correspondiente en la fuente de datos. Veamos otro ejemplo pero esta vez con número:

for number in 1...5 {
    
    print("Número: \(number)")

}

En este caso la fuente de datos es proporcionada por el operador de rango cerrado, la salida en pantalla:

Número: 1
Número: 2
Número: 3
Número: 4
Número: 5

where

Esto nos da una idea de cuan flexible puede ser un bucle for in, incluso podemos aplicar where sobre él:

for number in 1...10 where number.isMultiple(of: 2) {
    
    print("Número: \(number)")

}

Este código se leería como: «Por cada número (number) en el rango de 1 a 10, donde este número sea múltiplo de 2, ejecuta el siguiente código». La salida en pantalla sería la siguiente:

Número: 2
Número: 4
Número: 6
Número: 8
Número: 10

while

Un bucle while ejecuta el código dentro de sus llaves mientras que la condición no devuelva false. Utilizamos este tipo de bucles cuando no conocemos el número de iteraciones y por ende las ejecuciones solo dependen de una expresión lógica.

Swift proporciona dos tipos de bucles while:

  • while, evalúa su condición al comienzo de cada ciclo del bucle.
  • repeat-while, evalúa su condición al final de cada ciclo del bucle.

Para ejemplificar el uso de while se me ha ocurrido este sencillo juego:

var ourHealthLevel = 100
var enemyHealthLevel = 100

var attackPower = 0

while ourHealthLevel > 1 && enemyHealthLevel > 1 {
    
    attackPower = Int.random(in: 5...50)
    
    ourHealthLevel -= attackPower
    
    print("😈 ⚔️ (\(attackPower)%) - Has sufrido un ataque! 😰 \(ourHealthLevel)% ❤️")
    
    attackPower = Int.random(in: 5...50)
    
    enemyHealthLevel -= attackPower
    
    print("😁 ⚔️ (\(attackPower)%) - Has atacado al enemigo! 👿 \(enemyHealthLevel)% ❤️")

} // while

if ourHealthLevel > 1 {
    
    print("\n😎 Has ganado la pela! 👿")
    
} else {
    
    print("\n😈 Has perdido la pelea! 😵")
    
}

En este juego peleamos contra un enemigo, nosotros atacamos y él nos ataca y con cada ataque a ambos nos disminuye la vida. Gana el que logre disminuir la vida del contrario por debajo de 1%.

Para que el juego sea justo la potencia de ataque se genera aleatoriamente entre 5% y 50%, dada esta característica quizás podamos vencer o perder en 4 jugadas o en 7, por ende los más adecuado sería utilizar un bucle while ya que no conocemos en cuantos ciclos terminará el juego.

Una posible salida en pantalla de este juego pudiera ser la siguiente:

😈 ⚔️ (31%) - Has sufrido un ataque! 😰 69% ❤️
😁 ⚔️ (17%) - Has atacado al enemigo! 👿 83% ❤️
😈 ⚔️ (38%) - Has sufrido un ataque! 😰 31% ❤️
😁 ⚔️ (45%) - Has atacado al enemigo! 👿 38% ❤️
😈 ⚔️ (19%) - Has sufrido un ataque! 😰 12% ❤️
😁 ⚔️ (24%) - Has atacado al enemigo! 👿 14% ❤️
😈 ⚔️ (20%) - Has sufrido un ataque! 😰 -8% ❤️
😁 ⚔️ (32%) - Has atacado al enemigo! 👿 -18% ❤️

😈 Has perdido la pelea! 😵

Analizando la salida en pantalla nos damos cuenta de un error. Una vez que nuestro enemigo (el azar) nos ataca y nos deja con -8% de vida, en lugar de acabar ahí la partida lo volvemos a atacar dejándolo entonces con un -18% de vida, vamos que si ya perdimos que sentido tiene.

break

Hay varias maneras de solucionar esto, una sería limitar los turnos a cada ciclo del bucle while y así nos cercioramos de que la expresión lógica se evalúa luego de cada ataque / turno. Otro enfoque sería comprobar nuestra vida luego del primer ataque y en caso de ser menor a 1 acabar la ejecución del bucle en ese momento mediante la instrucción break.

La palabra clave break nos permite interrumpir la ejecución de cualquier bucle, ya sea un for in, un while o un repeat while.

Vamos a probar la última alternativa que hemos propuesto, que es a su vez la que menos código nos obliga a refactorizar:

var ourHealthLevel = 100
var enemyHealthLevel = 100

var attackPower = 0

while enemyHealthLevel > 1 {
    
    attackPower = Int.random(in: 5...50)
    
    ourHealthLevel -= attackPower
    
    print("😈 ⚔️ (\(attackPower)%) - Has sufrido un ataque! 😰 \(ourHealthLevel)% ❤️")
    
    if ourHealthLevel < 1 {
        
        break
        
    } // if
    
    attackPower = Int.random(in: 5...50)
    
    enemyHealthLevel -= attackPower
    
    print("😁 ⚔️ (\(attackPower)%) - Has atacado al enemigo! 👿 \(enemyHealthLevel)% ❤️")

} // while

if ourHealthLevel > 1 {
    
    print("\n😎 Has ganado la pela! 👿")
    
} else {
    
    print("\n😈 Has perdido la pelea! 😵")
    
}

Así quedaría nuestro código ya modificado, y aquí nuevamente la salida en pantalla pero esta vez funcionando con la logica correcta:

😈 ⚔️ (7%) - Has sufrido un ataque! 😰 93% ❤️
😁 ⚔️ (41%) - Has atacado al enemigo! 👿 59% ❤️
😈 ⚔️ (21%) - Has sufrido un ataque! 😰 72% ❤️
😁 ⚔️ (32%) - Has atacado al enemigo! 👿 27% ❤️
😈 ⚔️ (38%) - Has sufrido un ataque! 😰 34% ❤️
😁 ⚔️ (48%) - Has atacado al enemigo! 👿 -21% ❤️

😎 Has ganado la pela! 👿

repeat while

El caso de repeat while es muy similar al de while, veamos un ejemplo:

var ourHealthLevel = 0
var enemyHealthLevel = 0

var attackPower = 0

repeat {
    
    attackPower = Int.random(in: 5...50)
    
    ourHealthLevel -= attackPower
    
    print("😈 ⚔️ (\(attackPower)%) - Has sufrido un ataque! 😰 \(ourHealthLevel)% ❤️")
    
    if ourHealthLevel < 1 {
        
        break
        
    } // if
    
    attackPower = Int.random(in: 5...50)
    
    enemyHealthLevel -= attackPower
    
    print("😁 ⚔️ (\(attackPower)%) - Has atacado al enemigo! 👿 \(enemyHealthLevel)% ❤️")

} while enemyHealthLevel > 1

if ourHealthLevel > 1 {
    
    print("\n😎 Has ganado la pela! 👿")
    
} else {
    
    print("\n😈 Has perdido la pelea! 😵")
    
}

Este es el ejemplo anterior modificado, en el cual hemos aplicado un repeat while y hemos establecido la vida de ambos jugadores a 0. No obstante a este tipo de bucle ejecuta el código al menos una vez antes de verificar la condición lógica, por este motivo es que obtenemos la siguiente salida:

😈 ⚔️ (23%) - Has sufrido un ataque! 😰 -23% ❤️

😈 Has perdido la pelea! 😵

Exacto, aún estando ambas vidas a cero el código se ejecuta y es entonces cuando se verifica la condición del while cuando se detiene el bucle. Estos bucles como todo son útiles ante ciertos escenarios, una más de las tantas herramientas que Swift pone a nuestra dispocición.

continue

La instrucción continue interrumpe la ejecución del ciclo actual del bucle, ya sea un for in, un while o un repeat while. Pero a diferencia de break el bucle continua su ejecución al comienzo de la siguiente iteración. Veamos un ejemplo:

for number in 1...20 {
    
    if number.isMultiple(of: 2) {
        
        continue
        
    } // if
    
    print(number)
    
} // for in

En este caso hacemos uso de la palabra clave continue para saltar la instrucción de print en caso de que number sea un número par. Por esto es que en la salida en pantalla:

1
3
5
7
9
11
13
15
17
19

no veremos ningún número par.

Conclusiones

Luego de lo que hemos comentado, cuando decimos flujo nos referimos al proceso que tenemos que seguir para lograr ejecutar una acción.

Tomando esto como base digamos que queremos ir del punto A al punto B en un carro, y dentro de una gran metrópolis. Dificilmente podremos ir de una y sin interrupciones, tendremos que ser capaces de tomar el camino más corto, doblando por aquí y por allá, frenando ante la luz roja de un semáforo, desviándonos si hay alguna obstrucción y así hasta llegar a nuestro destino, el punto B.

En el desarrollo de aplicaciones es similar, el usuario navega por el flujo de acciones de nuestra aplicación, pero estas acciones siempre cuentan con una interfaz donde el usuario puede establecer ciertas opciones y filtros que luego de validadas por nuestro código terminan por matizar la ejecución de dicha acción.

Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo y síguenos en nuestras redes sociales. Mantente al tanto de todas nuestras publicaciones.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

¡Cualquier duda o sugerencia, ya sea errores a corregir o ejemplos a añadir, será más que bienvenida, necesaria!

Deja una respuesta

Su dirección de correo electrónico no será publicada.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para mejorar su experiencia. Asumiremos que está de acuerdo con esto, pero puede optar por no participar si lo desea. Aceptar Leer Más

RECIBE CONTENIDO SIMILAR EN TU CORREO

RECIBE CONTENIDO SIMILAR EN TU CORREO

Suscríbete a nuestra lista de correo y mantente actualizado con las nuevas publicaciones.

Se ha suscrito correctamente!