Qué es un lenguaje de programación

Un lenguaje de programación es un lenguaje que se utiliza para dar instrucciones a una computadora, ordenador o cualquier otro dispositivo que posea un microprocesador con el fin de que éste pueda resolver determinadas tareas. Dicho de otra manera, un lenguaje de programación es el elemento que tienen los programadores para darle ordenes a las computadoras.

Ahora bien, el microprocesador por si solo es incapaz de entender estos lenguajes de programación tal cual los escribimos. Entonces, ¿cómo se entiende esto? Veámoslo.

Lenguaje máquina

El lenguaje que entienden los microprocesadores se llama Lenguaje máquina. Este lenguaje es binario, lo que implica que solo entiende de 1 y 0. En realidad, estos dos valores corresponden a la simbología usada para representar los dos niveles de tensión que acepta el microprocesador. La ventaja de usar solo dos valores es la posibilidad de usar el Algebra booleana y el sistema binario, que permite representar valores con secuencias de unos y ceros.

Sin embargo, programar con ceros y unos se vuelve prácticamente imposible para los seres humanos, ya que este tipo de lenguajes es muy distinto al que estamos acostumbrados.

Lenguaje ensamblador. El primer lenguaje de programación

Este lenguaje, también conocido como Assembler, es el más parecido al lenguaje máquina que vamos a encontrar. Fue de los primeros lenguajes que se utilizaron para hacer programas y hoy en día solo se usa para fines académicos o de investigación.

Cada uno de los códigos binarios que el microprocesador acepta está representado por determinadas instrucciones dentro de este lenguaje. Si viéramos un programa escrito en este lenguaje sin tener previo conocimiento del mismo no entenderíamos lo que se le está ordenando al sistema, ya que las operaciones que se utilizan son MOV, INT , POP, PUSH, etcétera.

Al utilizar el lenguaje ensamblador, el programador define instrucciones a nivel de hardware, en donde, por ejemplo, hace referencia a posiciones de memoria o a los registros de la CPU. Esto hace que a este tipo de lenguajes se lo conozca como de bajo nivel.

Finalmente, para que el sistema pueda entender estos comandos, es necesario utilizar unos programas llamados ensambladores que convierten estas instrucciones en lenguaje máquina.

Lenguajes de programación de alto nivel

Estos lenguajes de programación se caracterizan por tener instrucciones fáciles de recordar ya que utilizan símbolos que existen en el lenguaje que utilizan las personas.

Además, estas instrucciones no están diseñadas para representar fielmente al lenguaje máquina sino que buscan orientarse a la capacidad cognitiva del ser humano.

Este tipo de lenguajes surgieron en los años 50 como una respuesta a las dificultades que generaba programar en lenguaje ensamblador. Una de las limitaciones más grandes que se encuentran en Assembler, además de su dificultad en el aprendizaje, es que cada procesador tiene sus propias instrucciones. Por lo tanto, un programa hecho para una cierta arquitectura debía ser modificado para poder portarse a otra.

Sin embargo, con los lenguajes de programación de alto nivel se abrieron varias posibilidades nuevas, como el uso de paradigmas de programación, la escritura de algoritmos más sencillos de comprender y la portabilidad de un programa a más de un sistema.

lenguajes de programación

Proceso de compilación

Como dijimos antes, los programas tal cual escribimos (siempre teniendo en cuenta que usamos lenguajes de alto nivel, ya que es el standard de hoy) no son comprendidos por la máquina.

Para que un sistema pueda ejecutarlos es necesario de un proceso intermedio que traduzca esas instrucciones de alto nivel a ceros y unos, de manera tal de generar un archivo binario que corresponde al lenguaje máquina. Este proceso se llama Compilación.

Por lo tanto, si una persona quiere programar debe primero disponer de un IDE que le permita no solo escribir código sino luego compilar al mismo. En ese proceso, la compilación no solo traduce a binario sino que en primer lugar busca errores de sintaxis como comprobación de que el algoritmo escrito se encuentra en condiciones de ser compilado.

El IDE que provee Apple para hacer programas para sus productos se llama Xcode. Con solo descargarlo del Mac App Store ya estamos en condiciones de escribir nuestros programas, validar si la sintaxis es correcta, compilar al mismo y ejecutarlo tanto en un dispositivo virtual como en un hardware real, como es el caso de un iPhone.

Lenguajes de programación interpretados

Cabe aclarar que no todos los lenguajes de programación requieren de compilación.

Esto no quiere decir que no se requiera validar si existen errores de programación o de sintaxis en el código, sino que la forma en la que se ejecutan estos programas es un poco distinto.

A diferencia de los lenguajes compilados, en los interpretados no existe un archivo binario resultante que deba ejecutarse para poder correr el programa. En cambio, a medida que se va ejecutando el código, el intérprete va traduciendo la instrucción en curso y procesándola, sin guardar un resultado de esa traducción. Por lo tanto, lo que se ejecuta no es un archivo binario sino un archivo de texto.

Un ejemplo claro de este tipo de lenguajes es Shell Script, que se ejecutan bajo sistemas Unix.

Lenguajes de programación que existen en la actualidad

Como era de esperarse, desde los años 50 hasta nuestros días se han creado una gran cantidad de lenguajes de programación para todos los gustos.

A continuación presentamos un listado de lenguajes que se usan en la actualidad:

  1. Swift
  2. Objective C
  3. Java
  4. C
  5. C++
  6. Python
  7. C#
  8. Visual Basic .NET
  9. Javascript
  10. PHP
  11. PERL
  12. Ruby
  13. PL/SQL

 

Name
Email
Review Title
Rating
Review Content
Qué es un lenguaje de programación
4,8 rating based on 12.345 ratings
Overall rating: 4.8 out of 5 based on 9 reviews.

Enumeraciones en Swift

En este capítulo veremos otro tipo de dato que podemos crear en nuestras aplicaciones y que serán muy útiles para resolver distintos tipos de problemas. A su vez, veremos la potencia que tienen las enumeraciones en Swift.

Definición de enumeraciones

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:

[code lang=”Swift”]
enum Enumeracion {
//Cuerpo de la enumeración
}
[/code]

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:

[code lang=”Swift”]
enum PuntoCardinal {
case norte
case sur
case este
case oeste
}
[/code]

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:

[code lang=”Swift”]
enum PuntoCardinal {
case norte, sur, este, oeste
}
[/code]

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:

[code lang=”Swift”]
var direccion = PuntoCardinal.norte
[/code]

La lectura de la asignación anterior es súper clara. Se está eligiendo un punto cardinal de un listado de posibles valores y guardándolo 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:

[code lang=”Swift”]
enum DiaDeLaSemana {
case lunes, martes, miercoles, jueves, viernes, sabado, domingo
}
[/code]

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

[code lang=”Swift”]
var mañana = DiaDeLaSemana.martes
[/code]

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:

[code lang=”Swift”]
mañana = .miercoles
[/code]

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:

[code lang=”Swift”]
if mañana == DiaDeLaSemana.lunes {
print(“Se termina el fin de semana!”)
}
[/code]

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

[code lang=”Swift”]
if mañana == .lunes {
print(“Se termina el fin de semana!”)
}
[/code]

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:

[code lang=”Swift”]
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
[/code]

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:

[code lang=”Swift”]
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
[/code]

Raw Values, valores crudos de las enumeraciones en Swift

Cuando creamos una enumeración y definimos sus posibles cases, cada case es en sí mismo un valor. Por el contrario, en otros lenguajes de programación como C, cada case está asociado a un valor entero. De esta manera, el primer case es igual a 0, el segundo es igual a 1 y así sucesivamente.

Si quisiéramos emular este comportamiento de C deberíamos asociar cada case a un valor, conocido como Raw Value o valor crudo por su traducción. Sin embargo, Swift es mucho más potente que C y permite asignar valores de distintos tipos, como String, Character, Enteros o números de coma flotante.

Los Raw Value son valores prepopulados, o sea, que al momento de definir la enumeración se le asigna el valor que cada case va a contener. Lo primero que debemos hacer para poder asociar estos valores es definir el tipo de dato a continuación del nombre de la enumeración (conocido como raw type) y luego asignar un raw value a cada miembro del enum, como vemos en el ejemplo:

[code lang=”Swift”]
enum Mascota: String {
case perro = “El mejor amigo del hombre”
case gato = “La mascota más suave”
case loro = “El más parlanchín”
}

let firulais = Mascota.perro
print(“\(firulais.rawValue)”)
//Devuelve
//El mejor amigo del hombre
[/code]

En el ejemplo, el Raw Type es String y cada una de las cadenas de texto asignadas a los cases conforman los Raw Values de cada uno.

Raw Values implícitos

Cuando trabajamos con enumeraciones que guardan valores crudos enteros o cadenas de texto, no es obligatorio definir un valor para cada case.

En el caso de los String, si no se define un valor, el raw value asociado será el texto del case.

[code lang=”Swift”]
enum DiaDeLaSemana:String {
case lunes, martes, miercoles, jueves, viernes, sabado, domingo
}

var mañana = DiaDeLaSemana.miercoles
print(“\(mañana.rawValue)”)

//Devuelve
//miercoles
[/code]

Por otro lado, si trabajamos con enumeraciones que almacenan enteros, si no definimos ningún valor asociado el primer case valdrá 0, el segundo valdrá 1 y asi sucesivamente (de la misma manera que funciona en C).

A su vez, si al primer miembro le asignamos un valor, por ejemplo 4, el segundo valdrá 5, el tercero 6 … es decir que cada miembro sucesivo sumará uno al anterior.

[code lang=”Swift”]
enum Saiyan: Int {
case Goku = 3, Vegeta, Gohan, Trunks
}

let principeSaiyan = Saiyan.Vegeta
print(“\(principeSaiyan.rawValue)”)

//Devuelve
//4
[/code]

HashValue en las enumeraciones en Swift

Como vimos, podemos asignar un Raw Value a cada case y corresponde al valor que este almacena. Estos Raw Value solo están disponibles si indicamos un Raw Type a la enumeración, tal cual vimos.

Sin embargo, en todo momento tenemos acceso al hashValue que corresponde al índice del case dentro de la enumeración. Esto es, la posición de cada miembro arrancando con 0 para el primero.

En este sentido, el hashValue es igual al rawValue cuando tenemos una enumeración con un Raw Type del tipo Int y no indicamos un valor para cada case (es decir, que usamos los implícitos).

[code lang=”Swift”]
print(“\(principeSaiyan.hashValue)”)

//Devuelve
//1
[/code]

El ejemplo anterior devuelve 1 ya que Goku se encuentra en la posición 0 y Vegeta en el 1.

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.

[code lang=”Swift”]
enum Saiyan: Int {
case Goku = 3, 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
[/code]

Valores asociados en las enumeraciones en Swift

En algunos casos es conveniente asociar determinada información relevante para cada miembro de una enumeración. Swift permite que en cada case se incluya información adicional al momento de crear una variable o constante del tipo de la enumeración en cuestión. Se dice que esa información adicional es un valor asociado al case que lo contiene.

Por ejemplo, supongamos que tenemos que realizar una aplicación para una escuela y necesitamos hacer un inventario del personal que trabaja en ella. Si se trata de un profesor queremos que se guarde la materia que dicta pero si se trata de un preceptor queremos el curso que administra, referenciándolo con un número entero (es decir, el preceptor puede ser de 5to año). En este sentido, podríamos usar el siguiente enum:

[code lang=”Swift”]
enum EmpleadoEscuela {
case profesor(String)
case preceptor(Int)
}

let Gabriel = EmpleadoEscuela.profesor(“Programación”)
let Lionel = EmpleadoEscuela.preceptor(5)

switch Gabriel {
case .profesor(let materia):
print(“El profesor dicta la materia \(materia)”)
case .preceptor(let curso):
print(“El preceptor administra el curso \(curso)”)
}

//Devuelve:
//El profesor dicta la materia Programación
[/code]

Como vemos en el ejemplo, cada case define un tipo de dato asociado para recibir un valor al momento de la creación de la variable. Dentro de un enum, cada miembro puede definir los valores asociados que requiera, incluso de distinto tipo entre ellos. A su vez, cada case puede tener 0, 1 o n valores asociados. Por ejemplo, si quisiéramos tener más datos del profesor, como nombre, apellido y materia podríamos solicitar tres String, como vemos a continuación:

[code lang=”Swift”]
enum EmpleadoEscuela {
case profesor(String,String,String)
case preceptor(Int)
}

let Lionel = EmpleadoEscuela.profesor(“Lionel”,”Messi”,”Magia”)

switch Lionel {
case .profesor(let nombre, let apellido, let materia):
print(“El profesor \(nombre) \(apellido) dicta la materia \(materia)”)
case .preceptor(let curso):
print(“El preceptor administra el curso \(curso)”)
}

//Devuelve:
//El profesor Lionel Messi dicta la materia Magia
[/code]

En el ejemplo anterior estamos usando let para tener una copia del valor asociado que contiene la variable. Como no estamos modificando su valor dentro del case, no necesitamos crear una variable, pero puede hacerse perfectamente en caso de requerirlo.

Por otro lado, si todos los valores asociados son extraídos de la misma forma, es decir, todos como constantes (let) o todos como variables (var) es posible escribir let o var una vez sola al principio. Por lo tanto, el siguiente Switch es equivalente al anterior:

[code lang=”Swift”]
switch Lionel {
case let .profesor(nombre, apellido, materia):
print(“El profesor \(nombre) \(apellido) dicta la materia \(materia)”)
case .preceptor(let curso):
print(“El preceptor administra el curso \(curso)”)
}
[/code]

Computed properties en enumeraciones en Swift

Las enumeraciones pueden definir también computed properties o propiedades computadas (calculadas), por su traducción. Estos son propiedades que devuelven un valor que ha sido calculado previamente.

Esta característica muestra otra diferencia entre la implementación de las enumeraciones en Swift versus otros lenguajes como C.

A continuación, veremos un ejemplo de un enum que incorpora una computed property para informar la profesión de cada case posible:

[code lang=”Swift”]
enum EmpleadoEscuela {
case profesor(String,String,String)
case preceptor(Int)

var profesion:String {
switch self {
case .profesor(_,_,let materia):
return “Profesor de \(materia)”
case .preceptor(_):
return “Preceptor”
}
}
}

print(“\(Lionel.profesion)”)

//Devuelve:
//Profesor de Magia
[/code]

En el ejemplo, definimos una propiedad calculada para obtener un String que informa de la profesión. En el primer case, como solo necesitamos el dato de la materia, omitimos los dos primeros valores usando un guión bajo. De esta manera, le indicamos al compilador que no vamos a hacer uso de ellos y por lo tanto no se reserva espacios en memoria de manera innecesaria.

Descarga el archivo playground.
Siguiente capítulo: Manejo de errores.

Name
Email
Review Title
Rating
Review Content
Enumeraciones en Swift
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 6 reviews.

GCD – Grand Central Dispatch: Tareas en segundo plano

Todas las aplicaciones que descargamos y usamos realizan un conjunto de operaciones con el fin de resolver un problema.

Como usuarios, lo primero que queremos es que esas apps no solo funcionen correctamente sino que su experiencia de uso sea agradable.

Sin dudas, el concepto de “agradable” incluye que la interfaz de usuario sea intuitiva y, sobre todo, fluida.

Una aplicación que demore en responder a la interacción del usuario genera cierto desconcierto y puede ser motivo suficiente para convencernos de que debemos eliminarla del dispositivo.

Para evitar que nuestras apps sea una de ellas, es muy importante comprender el concepto de las tareas en segundo plano y aprender sobre GCD.

GCD: Grand Central Dispatch

Cuando hablamos de GCD estamos haciendo referencia a una librería a nivel de sistema que nos permite manejar operaciones concurrentes.

Esto es posible físicamente ya que los dispositivos actuales poseen más de un procesador y por lo tanto, es posible ejecutar en uno de ellos todo el código correspondiente al hilo principal y dejar para el otro (o los otros) las operaciones en segundo plano.

Una aclaración al respecto: las operaciones en background no son exclusivas de los sistemas multi-core. De hecho, existen otras técnicas basadas en software que permiten ejecutar operaciones concurrentes utilizando solo un procesador.

Todo programa esta compuesto por una serie de instrucciones que el procesador debe ejecutar. A esto se le llama thread (o hilo en español).

Por default, todas las instrucciones que escribimos se ejecutan en el hilo principal, una tras otra, y en caso de necesitarlo, es posible crear otros hilos que se ejecutan de manera independiente en otros procesadores.

GCD nos da la posibilidad de no tener que lidiar con la gestión, control y eliminación de threads ni con la necesidad de controlar si se ha llegado al numero máximo permitido.

Simplemente declaramos qué necesitamos correr en segundo plano y lo ejecutamos.

Existe una ley fundamental al momento de usar GCD: todas las operaciones que corresponden a la interfaz de usuario deben ejecutarse en el hilo principal y se deben dejar en segundo plano a todas aquellas que requieran un tiempo considerable de ejecución, como puede ser la descarga de un archivo por la web o la actualización de una base de datos.

En el caso de que se necesite actualizar la interfaz de usuario luego de realizar alguna operación en background, se debe volver al hilo principal y hacer las operaciones correspondientes.

En el inicio de Swift, allá por el 2014, para poder utilizar estas funcionalidades se debían usar algunas instrucciones heredadas de Objective C. Sin embargo, desde la versión 3 del lenguaje, se han incorporado nuevas maneras de utilizar los trabajos en segundo plano que, por cierto, lo hacen mucho más simple.

Terminologia básica. Tasks y Queues

En este post introductorio de GCD vamos a enfocarnos solamente en los conceptos necesarios para poder usar tareas en segundo plano de una manera sencilla.

Dado que la librería en cuestión ofrece un sinfín de posibilidades, podríamos extendernos hablando de sus características, pero creo que para esta primera entrega es suficiente con presentar el tema y dar las herramientas básicas para poder usarlo.

Lo primero que tenemos que hacer al momento de pedirle al sistema que ejecute en background una serie de instrucciones es definir una Task.

Para ser breve, una task es un closure (cerradura en español), o sea, un conjunto de instrucciones definido dentro de un bloque, que permite ser pasado por parámetro a una función y captura los elementos “que lo rodea”, es decir, puede utilizar las variables que se encuentran fuera de su ámbito y recordar su valor.

Una vez que tenemos nuestro código a ejecutar, nuestra Task (o tarea), para poder indicarle a GCD que queremos que sea ejecutada en segundo plano debemos utilizar unas Queues (en español serían colas).

GCD provee lo que se llama Dispatch Queues, o sea, despacho de colas, que se encargan de manejar las tareas, recibiéndolas y ejecutándolas a medida que el procesador dispone de “espacio”.

El orden en que se ejecutan las mismas es PEPS (primero que entra, primero que sale. También conocido como FIFO en Ingles, First In First Out), esto implica que las tareas se van a ir ejecutando en el orden en que fueron agregadas a la cola.

Si bien se puede hacer uso de distintos tipos de queues (unos más prioritarios que otros), por ser el primer acercamiento al tema vamos a usar el queue global con un nivel de prioridad default.

Algo a tener en cuenta es que esta queue también es usado por las APIs de Apple, con lo cual nuestro código no es lo único que va a estar corriendo ahí.

Por último, como ya dijimos anteriormente, cada vez que se requiera actualizar elementos de la interfaz de usuario es necesario usar el thread principal. Para eso, GCD provee una queue que se ejecuta en ese plano y se llama Main.

Manos a la obra

Para pedirle al sistema que ejecute una tarea en segundo plano, debemos ejecutar lo siguiente:

[code lang=”Swift”]
DispatchQueue.global().async {

//Código a ejecutar el segundo plano

}
[/code]

En el caso en el que se requiera actualizar elementos de la interfaz de usuario, se debe invocar al main thread:

[code lang=”Swift”]
DispatchQueue.global().async {
//Código a ejecutar el segundo plano

DispatchQueue.main.async {
//Codigo para actualizar la UI
}
}
[/code]

Existe otra manera de ejecutar tareas en segundo plano indicando un nombre al queue, lo cual nos va a ayudar al momento de debuguear nuestro código ya que aparecerá con el nombre que le indiquemos en el listado de procesos que nos muestra Xcode:

[code lang=”Swift”]
let queue = DispatchQueue(label: “com.ejemplo.introduccionGCD”)
queue.async {
//Código a ejecutar el segundo plano

DispatchQueue.main.async {
//Codigo para actualizar la UI
}
}
[/code]

Esto es todo por este primer post sobre GCD. La idea era que sirviera como introducción al tema y nos permitiera empezar a usar tareas en segundo plano desde ahora.

Name
Email
Review Title
Rating
Review Content
GCD - Grand Central Dispatch: Tareas en segundo plano
4,9 rating based on 12.345 ratings
Overall rating: 4.9 out of 5 based on 108 reviews.

Referencias Strong, weak y unowned: ARC en acción

A menudo solemos entrar en este tema cuando vemos en el debugger que nuestra aplicación está consumiendo una determinada cantidad de memoria y la misma nunca es liberada.

Básicamente, en la mayoría de los casos, se trata de referencias cíclicas entre clases que no le permiten al ARC poder liberarlas de la memoria. Veamos de qué se trata.

ARC: Automatic Reference Counting (Contador de referencias automático)

El contador de referencias automático es una funcionalidad que permite liberar la memoria de aquellos elementos que no posean referencias fuertes hacia ellos (strong references).

Esto es, cada vez que creamos una nueva referencia a una clase, ARC reserva un espacio en memoria para poder guardar el tipo de instancia y los valores de cada una de las propiedades del objeto.

En el momento en que esa referencia es nil y no existe ninguna otra referencia hacia ese objeto, ARC libera la memoria que estaba siendo ocupaba para poder usar ese espacio para otros elementos.

Para estar seguros de que no se está liberando memoria que está siendo utilizada por alguna parte del código, ARC chequea que la cantidad de referencias strong hacia ese elemento sean 0.

Supongamos que tenemos esta clase y definimos 3 variables opcionales de ella:

[code lang=”Swift”]
class Cliente {
let nombre:String
init(nombre:String)
{
self.nombre = nombre
print(“Instancia cliente creada”)
}

deinit {
print(“Instancia borrada de la memoria”)
}
}

var cliente1: Cliente?
var cliente2: Cliente?
var cliente3: Cliente?
[/code]

Al tratarse de variables opcionales, las mismas son inicializadas en nil y no referencian a ningún objeto de la clase Cliente. A continuación, instanciamos la primera:

[code lang=”Swift”]
cliente1 = Cliente(nombre: “Gabriel”)
[/code]

En este momento, el sistema reserva memoria para alocar el objeto cliente1 (se imprime el mensaje “Instancia cliente creada”). Si a su vez, creamos otras dos referencias a este objeto logramos obtener 3 referencias hacia la misma posición de memoria:

[code lang=”Swift”]
cliente2 = cliente1
cliente3 = cliente1
[/code]

Esto implicaría que si se modificara el nombre de la referencia cliente2, por ejemplo, se afectaría también a cliente1 y cliente3.

A esta altura, lo que está sucediendo es que tenemos 3 referencias strong a la misma instancia Cliente. Por lo tanto, si una o dos de ellas se igualaran a nil, la instancia seguiría existiendo:

[code lang=”Swift”]
cliente1 = nil
cliente2 = nil
[/code]

Por este motivo, ARC no puede desasignar esa posición de memoria hasta tanto todas las referencias fuertes hacia ella se eliminen. Luego de ejecutarse la siguiente sentencia, se imprimiría por fin el mensaje “Instancia borrada de la memoria”:

[code lang=”Swift”]
cliente3 = nil
[/code]

El problema más común: referencias strong entre instancias

Como ya comentamos, una vez que ARC detecta que una instancia tiene 0 referencias strong, elimina ese contenido de la memoria para poder ser utilizada nuevamente.

El problema es que es factible escribir código en el cual se llegue a un punto en el que nunca sea posible llegar a 0 referencias strong, y por ese motivo esos elementos nunca se eliminen de la memoria.

Supongamos que tenemos dos clases como las siguientes y creamos una variable opcional de cada una de ellas:

[code lang=”Swift”]
class Persona {
let nombre:String

init(nombre:String)
{
self.nombre = nombre
print(“Instancia Persona creada”)
}

var vehiculo:Vehiculo?

deinit {
print(“Instancia Persona borrada de la memoria”)
}
}

class Vehiculo {
let tipo:String

init(tipo:String)
{
self.tipo = tipo
print(“Instancia Vehiculo creada”)
}

var dueño:Persona?

deinit {
print(“Instancia Vehiculo borrada de la memoria”)
}
}

var persona1:Persona?
var vehiculo1:Vehiculo?
[/code]

Como bien dijimos anteriormente, a esta altura son simplemente variables sin contenido ya que no están apuntando a ninguna posición de la memoria. En el momento en que las instanciamos:

[code lang=”Swift”]
persona1 = Persona(nombre: “Gabriel”)
vehiculo1 = Vehiculo(tipo: “Camioneta”)
[/code]

estaríamos teniendo lo siguiente:

Automatic Reference Counting

Es decir, cada variable estaría utilizando una referencia strong a la posición de memoria a la cual están apuntando.

En este momento, se pueden linkear cada una de las instancias entre sí de la siguiente manera; dado que las variables son opcionales, se pueden “desenvolver” (unwrap) utilizando el signo de exclamación:

[code lang=”Swift”]
persona1!.vehiculo = vehiculo1
vehiculo1!.dueño = persona1
[/code]

Así es como se vería lo que tenemos por ahora:

Automatic Reference Counting

Lo que está sucediendo en este instante es que cada instancia crea una referencia fuerte (strong) hacia la otra, logrando que cada una tenga un total de dos referencias strong.

Este es un escenario muy común en muchos diseños de clases y en muchas ocasiones es difícil para el desarrollador prever esta situación de antemano. El problema, es que cuando se rompen las referencias hacia esas posiciones de memoria, la suma de referencias strong no suman 0:

Automatic Reference Counting

Por lo tanto, esas posiciones de memoria quedan ocupadas y no puede liberarse el espacio.

¿Cómo resolver este problema entonces?

Para poder evitar el inconveniente de las referencias strong cíclicas, swift nos provee dos tipos más de referencias: weak y unowned. Ambas dos permiten apuntar a una instancia en memoria sin mantener una referencia fuerte hacia el mismo, lo que permite que ARC pueda liberar la memoria al momento de romper la única referencia strong que queda.

La diferencia que existe entre ellas es que weak es válido para aquellas variables que pueden ser nil en algún momento de la ejecución del programa (se utiliza con opcionales) y en cambio unowned, se utiliza para variables que siempre tienen un valor.

Resolviendo la problemática con weak

Volviendo al ejemplo anterior, teniendo en cuenta que un vehículo puede o no tener un dueño, se puede modificar la clase de la siguiente manera:

[code lang=”Swift”]
class Vehiculo {
let tipo:String

init(tipo:String)
{
self.tipo = tipo
print(“Instancia Vehiculo creada”)
}

weak var dueño:Persona?

deinit {
print(“Instancia Vehiculo borrada de la memoria”)
}
}
[/code]

En ese caso, la relación que estaríamos consiguiendo sería la siguiente:

Automatic Reference Counting

Por lo tanto, la variable persona1 tiene una sola referencia strong, ya que la segunda es weak. Al igualar la misma a nil estaríamos logrando que ARC libere la memoria ya que la sumatoria de referencias fuertes es igual a 0:

[code lang=”Swift”]
persona1 = nil
[/code]

En este momento, solo estaríamos teniendo la variable vehiculo1 con una sola referencia fuerte, ya que la otra fue eliminada por ARC en el paso anterior. De la misma manera, igualando ésta a nil estaríamos liberando la memoria completamente:

[code lang=”Swift”]
vehiculo1 = nil
[/code]

Esta solución es posible ya que se planteó que un vehículo puede o no tener un dueño, con lo cual la variable “dueño” es opcional (en alguna parte de su ciclo de vida puede valer nil).

Este concepto es muy importante ya que está relacionado con la forma en la que el sistema trata a estas referencias. Como las mismas no mantienen una referencia strong al elemento al que apuntan, es posible que esa instancia sea eliminada de la memoria mientras la referencia weak está apuntando al elemento en cuestión. Es en ese momento cuando ARC iguala a esa variable a nil, para poder liberar ese espacio. Por lo tanto, se debe tratar siempre de variables y no de constantes, ya que de lo contrario ARC nunca podría liberar la memoria.

Resolviendo la problemática con unowned

Al momento de definir el diagrama de clases es importante entender que, si se puede dar un caso de referencias cíclicas, hay que tener cuidado con las referencias strong. Ya vimos que si la variable puede llegar a ser nil en algún momento, entonces ella debe ser weak. Esto le va a permitir a ARC poder igualarlo a nil al momento de liberar el espacio en memoria. Sin embargo, hay situaciones en las cuales la referencia siempre va a tener un valor durante su ciclo de vida, y es en esas situaciones en las cuales podemos resolver este problema usando referencias unowned.

Suponiendo que el ejemplo anterior se utiliza para un sistema que permite llevar un inventario de corredores de carreras Formula 1, en donde cada piloto (clase Persona) siempre tiene un auto (clase Vehiculo) y a su vez el vehículo siempre tiene un piloto, la forma de moldear la clase sería la siguiente:

[code lang=”Swift”]
class Vehiculo {
let tipo:String

init(tipo:String, dueño:Persona)
{
self.tipo = tipo
self.dueño = dueño
print(“Instancia Vehiculo creada”)
}

unowned var dueño:Persona

deinit {
print(“Instancia Vehiculo borrada de la memoria”)
}
}
[/code]

Un dato a tener en cuenta es que, como la variable dueño siempre tiene que tener un valor (por ser declarada como unowned), no puede ser opcional. Por ese motivo, debe ser inicializada si o si dentro de init.

El resto de la operativa es idéntica a los ejemplos anteriores y la forma en la que se verían las relaciones tendrían este aspecto:

Automatic Reference Counting

Conclusiones

  • Si no se especifica la referencia al crear una variable, la misma es strong
  • ARC libera la memoria cuando la cantidad de referencias strong es igual a 0
  • Para resolver el problema de las referencias cíclicas strong, se utilizan referencias weak y unowned
  • Weak se debe usar cuando el atributo puede valer nil en alguna parte de su ciclo de vida
  • Unowned se debe usar cuando está garantizado que ese atributo siempre tendrá un valor
Name
Email
Review Title
Rating
Review Content
Referencias Strong, weak y unowned: ARC en acción
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 9 reviews.

Estructuras en Swift

Swift provee otro tipo de dato que el programador puede utilizar para crear tipos compuestos, muy parecidos a las clases pero que tienen algunas diferencias. En este capítulo vamos a estar hablando de las estructuras en Swift. Como muchos de los puntos ya fueron explicados en el capítulo de clases, trataremos de ser más concisos esta vez.

Definición de estructura

Si quisiéramos definir de una forma rápida qué es una estructura, podríamos decir que se trata de un tipo de dato que permite agrupar varios elementos de distinto tipo bajo un mismo nombre. En este sentido, podríamos tener una estructura compuesta un por un String, un Int y un Bool, por solo dar un ejemplo.

Si bien esto es cierto para la mayoría de los lenguajes de programación, en Swift una estructura es muy parecida a las clases ya que poseen varios puntos en común. Uno de los más importantes es que se pueden incluir métodos o funciones a las estructuras en Swift.

Por lo tanto, una mejor definición sería que las estructuras, al igual que las clases, son bloques flexibles y de propósito general que definen el comportamiento general de una aplicación.

A su vez, existen varias diferencias entre las clases y estructuras. Una de las más importantes es que las estructuras no permiten la herencia entre ellas, como vimos en el capítulo de Clases en Swift.

Estructuras en Swift – Sintaxis

Para poder crear una estructura se utiliza la palabra reservada struct seguido del nombre de la misma. Como se trata de un tipo de dato, se espera, según la convención de nombres, que su nombre comience con una letra mayúscula:

[code lang=”Swift”]
struct UnaEstructura {

//Definición de la estructura

}
[/code]

De manera contraria, todas las funciones que la estructura contenga deberán nombrarse usando una letra en minúscula al inicio. En todos los casos, si los nombres a usar contienen más de una palabra, no se deberán usar ningún separador entre ellas (como guiones o barras) sino que se deberá usar mayúsculas para la primer letra de cada palabra (notación camel case).

Veamos un ejemplo de una estructura:

[code lang=”Swift”]
struct Ubicacion {
var latitud:Double = -34.599722
var longitud:Double = -58.381944
}
[/code]

En este caso estamos creando una estructura llamada Ubicación que contiene dos variables del tipo Double; una para marcar la longitud y otro para la latitud, a los cuales se les asigna un valor correspondiente a la ubicación de Buenos Aires en Argentina.

Estructuras en Swift – proceso de inicialización

Como vimos en el capítulo de clases, crear una instancia implica asignar un espacio en memoria para que la clase o estructura tenga entidad propia y así poder usar sus elementos. La forma de hacerlo es crear una variable o constante del tipo de esa clase o estructura y usar de ahí en más la variable o constante en cuestión, haciendo uso de todos los elementos que fueron declarados y programados para esa clase o estructura.

Inicializador por defecto

La forma más simple y básica de crear una instancia de una estructura es mediante el uso del nombre de la misma seguido de paréntesis, lo que corresponde al inicializador por defecto, como vemos a continuación:

[code lang=”Swift”]
let buenosAires = Ubicacion()
[/code]

En el ejemplo, estamos creando una constante llamada buenosAires a la cual se le asigna una estructura del tipo Ubicación.

Para poder acceder a sus atributos, simplemente hacemos referencia a ellos usando un punto seguido de la propiedad que queremos usar:

[code lang=”Swift”]
print(“Buenos Aires se sitúa en la longitud \(buenosAires.longitud)”)

//Devuelve:
//Buenos Aires se sitúa en la longitud -58.381944
[/code]

Los inicializadores por defecto pueden usarse para aquellas estructuras que no tengan definido un inicializador y que tengan todas sus propiedades asignadas a valores por default. Esto se debe a que el inicializador por defecto crea una instancia con todas las propiedades asignadas a esos valores ya definidos en la estructura.

Inicializador de miembro

En el caso de tener una estructura a la cual no se le haya definido ningún inicializador, se puede hacer uso del inicializador de miembro, el cual permite pasar por parámetro los valores iniciales de las propiedades al momento de crear la instancia.

A diferencia del inicializador por defecto, en este caso podemos usarlo incluso si las propiedades de la estructura no tienen valores default.

En este sentido, usando el ejemplo de arriba, podemos generar una instancia de la siguiente manera:

[code lang=”Swift”]
let unaUbicacion = Ubicacion(latitud: -34.454345345, longitud: -53.34234235)
[/code]

Inicializador personalizado

En muchos casos, al momento de crear una instancia necesitamos editar algunos puntos del comportamiento del mismo para realizar cálculos usando los valores pasados por parámetro para las propiedades, en general para completar otras propiedades cuyo valor es calculable.

Para poder resolver esta problemática, tenemos los llamados inicializadores personalizados, que son unas funciones que se ejecutan al momento de crear la instancia de la estructura, en donde podemos customizar el comportamiento a nuestro antojo. Para ello, usamos la palabra reservada init seguida de paréntesis:

[code lang=”Swift”]
struct Ubicacion {
var latitud:Double = -34.599722
var longitud:Double = -58.381944
var hemisferio:String?

init(latitud:Double, longitud:Double) {
self.latitud = latitud
self.longitud = longitud

if latitud > 0 {
hemisferio = “Norte”
} else {
hemisferio = “Sur”
}
}
}
[/code]

Como vemos, estamos creando un inicializador que recibe por parámetro una latitud y longitud de manera de poder sobreescribir los valores por defecto que tienen las propiedades de la estructura (las cuales son referenciadas inequívocamente usando la palabra reservada self). Hasta aquí, estamos emulando el inicializador de miembro visto anteriormente. Sin embargo, la funcionalidad extra que estamos agregando es la de decidir si la ubicación en cuestión corresponde al hemisferio Norte o al Sur.

Cabe destacar, que si creamos un inicializador personalizado, ya no podremos usar los otros vistos anteriormente (el de miembro y el que Swift provee por defecto). En el caso de querer usarlos de todas maneras, tendremos que programarlos.

Un punto muy importante a tener en cuenta es que si alguno de los inicializadores creados por nosotros tiene la misma definición que el inicializador de miembro, no podremos usar ambos al mismo tiempo ya que entrarían en conflicto. Para más información, ver el capítulo de funciones en Swift.

La siguiente estructura corresponde a la misma que venimos trabajando a la cual se le agrega el inicializador por defecto únicamente, ya que el otro no puede ser incluido como tal por lo explicado en el párrafo anterior:

[code lang=”Swift”]
struct Ubicacion {

var latitud:Double = -34.599722
var longitud:Double = -58.381944
var hemisferio:String?

init(latitud:Double, longitud:Double) {
self.latitud = latitud
self.longitud = longitud

if latitud > 0 {
hemisferio = “Norte”
} else {
hemisferio = “Sur”
}
}

//Inicializador por defecto
init() {

}

/*
Este no puede ser incluido ya que tiene la misma definición que el primer inicializador
//Inicializador de miembro
init(latitud:Double, longitud:Double) {
self.latitud = latitud
self.longitud = longitud
}
*/

}
[/code]

Estructura en Swift – tipos por valor

Las estructuras, al igual que las enumeraciones que veremos en un capítulo posterior, son tipos por valor. Esto quiere decir que cada vez que asignamos una estructura a una variable o constante, o cuando esa estructura es pasada por parámetro a una función, el valor es copiado.

Este concepto es muy importante y para entenderlo bien lo vamos a ver con un ejemplo. Supongamos que tenemos una estructura Empleado y que creamos una instancia de ella:

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int
}

var steve = Empleado(nombre: “Steve”, edad: 50)
[/code]

Lo que sucede es que en la memoria se está creando un espacio en donde se guardan los datos de la instancia steve. Si copiamos esa estructura en otra constante o variable:

[code lang=”Swift”]
let otroSteve = steve
[/code]

lo que estamos logrando es la creación de otro espacio en memoria distinto en donde se copian todos los datos de la estructura original. Por lo tanto, si modificamos uno de ellos, el otro no se ve afectado por tratarse de instancias distintas que apuntan a espacios de memoria distintos.

[code lang=”Swift”]
steve.nombre = “Bill”

print(“\(steve.nombre)”) // Bill
print(“\(otroSteve.nombre)”) // Steve
[/code]

Este comportamiento es completamente distinto al de las clases ya que éstas son tipos por referencia, lo que genera que una copia de una clase en otra provoque que ambas apunten a la misma dirección de memoria y por lo tanto, al cambiar algo en una de ellas, se modifique en la otra.

Estructuras en Swift – métodos

Como ya vimos en el capítulo anterior, los métodos son funciones que podemos agregar a nuestras estructuras y forman parte del comportamiento que la misma posee. En pocas palabras, definen lo que esa estructura es capaz de hacer.

Nota
Todas las reglas aplicadas a los funciones de una estructura son las mismas que vimos en el capitulo de funciones, como las etiquetas de argumento, los tipos de parámetros que existen, el uso de self, etcétera. Por lo tanto, recomendamos releerlo en caso de ser necesario.

 

Métodos de instancia comunes

Son los métodos más simples que vamos a encontrar en cualquier código Swift y se caracterizan por no modificar ningún elemento de la estructura. En general devuelven un resultado o realizan una impresión por consola.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print(“Hola \(nombre)!”)
}
}

steve.saludar()

//Devuelve
//Hola Bill!
[/code]

Métodos de instancia que modifican a la estructura

Como fue explicado párrafos atrás, las estructuras son tipos por valor. Esto provoca que por defecto sus propiedades no puedan ser modificadas dentro de la estructura por sus propios métodos.

Sin embargo, en caso de necesitar modificar estos valores dentro de la estructura mediante alguno de sus métodos de instancia, se debe marcar a la misma con la palabra reservada mutating.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print(“Hola \(nombre)!”)
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}
}
[/code]

En el ejemplo anterior, agregamos una función para poder cambiar la edad del empleado. Como la edad es una propiedad, incluso si la misma está declarada como variable, su modificación no es permitida por default y por lo tanto debemos usar la palabra mutating para modificar este comportamiento.

[code lang=”Swift”]
steve.cambiarEdad(por: 55)
[/code]

Modificación de la estructura completa

Otra opción que nos permite Swift es la posibilidad de modificar completamente la estructura desde sus métodos de instancia usando la propiedad self. Lo que hace esta técnica es crear una nueva instancia y reemplazar a la ya existente.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print(“Hola \(nombre)!”)
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}

mutating func cambiarEmpleado(a nuevoEmpleado:Empleado) {
self = nuevoEmpleado
}
}

steve.cambiarEmpleado(a: Empleado(nombre: “Gabriel”, edad: 30))
steve.saludar()

//Devuelve:
//Hola Gabriel!
[/code]

Lo que sucede en ejemplo es que creamos una nueva función que recibe por parámetro a un Empleado (al estilo de las funciones recursivas que reciben un tipo de dato semejante a ellas) y mediante el uso de self modificamos completamente los valores de la estructura steve. Como estamos modificando su valor, debemos forzosamente utilizar la palabra mutating.

Información adicional
Estos métodos se llaman de instancia porque solamente pueden ser usados una vez que se ha asignado el espacio en memoria correspondiente a la estructura. Su contrapartida son los métodos de tipo.

 

Métodos de tipo

Este tipo de métodos, a diferencia de los de instancia, pueden ser invocados sin necesidad de crear una instancia de esa estructura. Por consiguiente, los métodos de tipo son invocados usando el nombre del tipo justamente. Para indicar que se va a usar este tipo de métodos, necesitamos incluir la palabra reservada static antes de func.

Estos métodos no son propiedad exclusiva de las estructuras, sino que también podemos usarlos en clases y enumeraciones.

[code lang=”Swift”]
struct Mensajes {
static let bienvenida = “Bienvenido a la empresa”
}

struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print(“Hola \(nombre)!”)
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}

mutating func cambiarEmpleado(a nuevoEmpleado:Empleado) {
self = nuevoEmpleado
}

func darBienvenida() {
print(“\(Mensajes.bienvenida) señor \(nombre)”)
}

}

steve.darBienvenida()

//Devuelve
//Bienvenido a la empresa señor Gabriel
[/code]

Como vemos, creamos un struct que contiene un string estático. Luego, en la estructura Empleado agregamos otra función para darle la bienvenida al empleado en cuestión y en ella hacemos uso de la estructura creada previamente pero sin instanciarlo. Directamente hacemos uso de su propiedad bienvenida sin haber hecho antes algo del estilo:

[code lang=”Swift”]
let mensaje = Mensajes()
[/code]

Esto es posible porque, como vimos, los componentes marcados como static pertenecen al tipo (en este caso al tipo Mensajes) y NO a la instancia.

Descarga el archivo playground.
Siguiente capítulo: Enumeraciones.

Name
Email
Review Title
Rating
Review Content
Estructuras en Swift
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 4 reviews.

Clases en Swift

Clases en Swift

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 capitulo vamos a ver las estructuras que tienen un comportamiento similar a las clases, sobre todo en Swift.

Clases – Definición

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 a 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 pensar a las propiedades como las características que poseen las clases mientras que los métodos son las acciones que pueden 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 resevada class seguida del nombre de la misma:

[code lang=”Swift”]
class UnaClase {

}
[/code]

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.

[code lang=”Swift”]
class Rectángulo {
let lados = 4
let alto = 5
let ancho = 2
}

let figura = Rectángulo()
print(“la figura contiene \(figura.lados) lados”)

//Devuelve
//la figura contiene 4 lados
[/code]

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.

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

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

let figura = Rectángulo(al: 3, an: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 3
[/code]

En el ejemplo usamos init para pedir 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 son guardados en las variables correspondientes.

Nota
En el proceso de inicialización, no solamente se puede dar valor de inicio 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 lineamientos de nomenclatura que promueve Apple. Uno de los puntos más importantes de estos lineamientos 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:

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

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

let figura = Rectángulo(al: 3, an: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 3
[/code]

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.

Nota
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 adentro de la clase:

[code lang=”Swift”]
class Rectángulo {
let lados = 4
var alto:Int
var ancho:Int

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

func incrementarAlto() {
alto += 1;
}
}

figura.incrementarAlto()
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 4
[/code]

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:

[code lang=”Swift”]
class Rectángulo {
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ángulo(alto: 3, ancho: 6)
figura.incrementarAlto(en: 6)
print(“La figura mide \(figura.ancho) x \(figura.alto)”)

//Devuelve:
//La figura mide 6 x 9
[/code]

Clases – 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 fueran definidos 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:

[code lang=”Swift”]
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
}
}
[/code]

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, los cuales dos de ellos no tienen código en su interior debido a que no se 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.

Nota
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:

[code lang=”Swift”]
class subClase: superClase {

}
[/code]

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

[code lang=”Swift”]
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;
}
}
[/code]

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.

[code lang=”Swift”]
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
[/code]

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:

[code lang=”Swift”]
class Círculo:FiguraGeometrica {
var radio: Double

init(color: String, radio:Double) {
self.radio = radio
super.init(color: color)
}
}
[/code]

Nota
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 sobrescriben las funcionalidades de los métodos (o mismo de las propiedades) para proveer una implementación alternativa en la subclase.

[code lang=”Swift”]
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)
}
}

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
}
}
[/code]

Tanto la clase Rectángulo como Círculo 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 sobrescribir la versión de la súper clase se usa la palabra reservada override.

[code lang=”Swift”]
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
[/code]

Descarga el archivo playground.
Siguiente capítulo: Estructuras.

Name
Email
Review Title
Rating
Review Content
Clases en Swift
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 6 reviews.

Cerraduras en Swift (closures)

swift closure

Definición de Cerraduras (closures)

Los closures son bloques de código que pueden ser pasados por parámetro a otras funciones a lo largo del código. Una de las ventajas que proveen es que son capaces de capturar y guardar referencias de las contantes y variables del contexto en que son definidos.

Las funciones, vistas en una entrada anterior, son casos especiales de closures, que tienen la característica de estar definidos bajo un nombre. Sin embargo, en algunos casos es necesario escribir algoritmos más cortos y específicos que no van a ser reutilizados o invocados varias veces sino que solo tienen sentido en un ámbito en particular. El caso más claro es el de funciones que aceptan funciones por parámetro, y que al momento de invocarlos se les pasa closures, sin nombre ni una declaración completa, sino simplemente el código de su implementación.

Las expresiones de cerraduras proveen muchas optimizaciones en su sintaxis permitiendo simplificarlas sin perder el sentido y la claridad.

Para hacer una analogía con otros lenguajes, una cerradura es similar a una expresión lambda o bloques de Objective C.

Sintaxis de una cerradura (closure)

La forma general de una cerradura es la siguiente:

[code lang=”Swift”]
{ (parametros) -> tipo de retorno in
sentencias
}
[/code]

Como se puede observar, su sintaxis es la de una función con la diferencia de que no posee un nombre que la defina y que agrega la palabra in para separar su declaración de su implementación. Los parámetros pueden ser in-out pero no pueden tener valores por defecto.

Supongamos que tenemos la siguiente función:

[code lang=”Swift”]
func calcular(a:Int, b:Int, operacion:(Int,Int) -> Int){
print(“El resultado es \(operacion(a,b))”)
}
[/code]

Para poder invocarla, necesitamos pasar dos números enteros y una función que reciba dos enteros y devuelva otro. Como vimos en el capitulo de funciones en swift, una posibilidad de hacerlo es definir una función que cumpla con el tipo del parámetro operación y pasarla por parámetro:

[code lang=”Swift”]
func sumar(a:Int, b:Int) -> Int{
return a+b
}

calcular(a: 6, b: 10, operacion: sumar)

//Devuelve:
//El resultado es 16
[/code]

Sin embargo, hacerlo de esta manera implica generar código que posiblemente no se vuelva a reutilizar y requiere de varias líneas para simplemente realizar una operación aritmética entre dos números.

En estos casos, es donde los closures vienen a ayudarnos a definir código mas especifico y, al mismo tiempo, nos permite ahorrarnos algunas líneas. Una forma de hacer lo mismo usando closures podría ser:

[code lang=”Swift”]
calcular(a: 6, b: 10, operacion: {(numero1:Int, numero2: Int) -> Int in return numero1 + numero2 })
[/code]

Simplificando la sintaxis de una cerradura

Lo primero que se puede simplificar al momento de usar un closure en la invocación de una función son los tipos de dato de sus parámetros y valor de retorno. En este sentido, Swift es capaz de inferir que el closure que estamos pasando debe cumplir con la forma (Int,Int) -> Int, por lo tanto no es necesario dejar explícitos los tipos de dato ni la flecha:

[code lang=”Swift”]
calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
return numero1 + numero2
})
[/code]

Además, los closures de una sola línea pueden omitir la palabra return ya que se sabe que la operación que se va a realizar dentro de ella se va a usar para devolver el dato que se espera:

[code lang=”Swift”]
calcular(a: 6, b: 10, operacion: {(numero1, numero2) in
numero1 + numero2
})
[/code]

Nota
Se dice que los closures son de una línea, o single-expression, cuando contienen una única operación independientemente de que hagamos cortes de línea por un tema de claridad al momento de leer el código.

Otra opción que podemos usar son los nombres de argumento corto que provee Swift para identificar a los argumentos, usando los nombres $0, $1, $2, etc, para hacer referencia al primer argumento, segundo, tercero, etc. Al mismo tiempo, se elimina la necesidad de usar la palabra reservada in ya que no se requiere separar la zona de argumentos de la de implementación:

[code lang=”Swift”]
calcular(a: 6, b: 10, operacion: { $0 + $1 })
[/code]

Métodos de operador en una cerradura

Existe una forma aún más corta de escribir el closure anterior y es haciendo uno se los métodos de operador. Toda clase o struct puede definir su propia implementación de los operadores existentes haciendo uso de una técnica llamada sobrecarga. En el caso del tipo de dato Int (que internamente es un struct) tiene definido al operador + como una función del tipo (Int, Int) -> Int que es justamente lo que espera el parámetro calcular. Por lo tanto, la invocación anterior puede hacerse de la siguiente manera:

[code lang=”Swift”]
calcular(a: 4, b: 5, operacion: +)
[/code]

Trailing Closures

Cada vez que escribimos una función que recibe un closure como parámetro, es recomendable dejar ese parámetro último en la lista para poder hacer uso de los trailing closures. El concepto de trailing hace alusión a que el closure puede escribirse por fuera del listado de parámetros, fuera de los paréntesis y a continuación de estos, de manera tal de poder escribir un código más claro y sin la necesidad de especificar el nombre del argumento. En el caso de que la función no reciba otro parámetro adicional más que el closure, se pueden omitir los paréntesis:

[code lang=”Swift”]
func funcionCualquiera(closure: () -> Void) {

}

//llamando a la función sin usar trailing Closure
funcionCualquiera(closure: {
//Codigo
})

//llamando a la función con trailing Closure
funcionCualquiera() {
//Codigo
}

//usando trailing closure sin los paréntesis
funcionCualquiera {
//Codigo
}
[/code]

Volviendo al ejemplo anterior, usando lo aprendido se debería escribir así:

[code lang=”Swift”]
calcular(a: 6, b: 10) { $0 + $1 }
[/code]

Capturar valores usando cerraduras

Como dijimos al principio, los closures pueden capturar valores del ámbito donde están definidos y hacer uso de esos valores incluso cuando este contexto ya no existe.

El ejemplo más claro es el caso de las funciones anidadas, donde la función interior captura las variables que pertenecen a la función que la engloba para realizar sus cálculos, incluso cuando solo se hace uso de la función interna.

Supongamos que tenemos una función que posee un atributo y una función anidada, y que al llamarla la misma devuelve a dicha función, de manera de poder ser utilizada fuera de su ámbito.

[code lang=”Swift”]
func hacerIncremento(en cantidad:Int) -> () -> Int {
var total = 0
func incrementar() -> Int {
total += cantidad
return total
}
return incrementar
}
[/code]

Como se puede observar, al invocar a hacerIncremento(en:) lo que obtenemos es una referencia a la función incrementar(_:), en lugar de un simple entero, la cual aumenta el valor del atributo total que pertenece a la función que la engloba. Cabe destacar que los atributos total y cantidad, si bien son usados dentro de la función incrementar(_:) no pertecen a ella, sino que ésta captura sus valores al momento de ejecutarse.

[code lang=”Swift”]
let incrementarPorDiez = hacerIncremento(en:10)

incrementarPorDiez()
//Devuelve 10

incrementarPorDiez()
//Devuelve 20

incrementarPorDiez()
//Devuelve 30

[/code]

Aquí hay un caso curioso. Como podemos ver, incrementarPorDiez es una constante con lo cual no debería poder cambiar su valor y sin embargo estamos observando que cada vez que lo ejecutamos aumenta en 10 su valor de retorno. Esto es posible porque los closures son tipos por referencia. Esto implica que cada vez que asignamos una constante o variable a un closure, en realidad lo que estamos haciendo es asignar la dirección de memoria donde ese closure existe a esa variable o constante, en lugar de hacer una copia fiel del mismo.

En otras palabras, cuando ejecutamos esta línea:

[code lang=”Swift”]
let incrementarPorDiez = hacerIncremento(en:10)
[/code]

Swift determina un espacio en memoria para que exista la función hacerIncremento(en:) y devuelve la dirección de memoria que es alojada en incrementarPorDiez. Si hiciéramos otra constante apuntando a otra versión distinta de hacerIncremento(en:) obtendríamos lo siguiente:

[code lang=”Swift”]
let incrementarPorCuatro = hacerIncremento(en: 4)

incrementarPorCuatro()
//Devuelve 4

incrementarPorCuatro()
//Devuelve 8

incrementarPorCuatro()
//Devuelve 12
[/code]

Como es un incrementador nuevo, la variable total es nueva también y arranca desde el principio, con lo cual, a estas alturas tenemos 2 incrementadores distintos en memoria. Ahora bien, si queremos asignar a una constante el primer incrementador, sucede lo siguiente:

[code lang=”Swift”]
let otroIncrementadorPorDiez = incrementarPorDiez

otroIncrementadorPorDiez()
//Devuelve 40
[/code]

Como vemos, no arranca de 0 sino de 30, que es donde había quedado el incrementador por 10. Esto sucede porque en la constante otroIncrementadorPorDiez lo que se está almacenando es la misma dirección de memoria a la que apunta incrementarPorDiez, con lo cual el atributo total al que están incrementando es el mismo.

Descarga el archivo playground.
Siguiente capítulo: Clases.

Name
Email
Review Title
Rating
Review Content
Cerraduras en Swift (closures)
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 4 reviews.

Opcionales en Swift

Opcionales Swift

Una de las ventajas que nos da Swift es la posibilidad de escribir código seguro.

Esto implica que este lenguaje está preparado para que el programador no se encuentre con situaciones indeseadas al momento de ejecutar su código, sino que tome los recaudos mientras codifica.

Los opcionales son un mecanismo muy importante que coopera en esta dirección.

Opcionales – Definición

Los opcionales permiten indicar que un valor puede estar ausente. Esto significa que una variable opcional puede tener un valor y por lo tanto se puede acceder al mismo o que esa variable esta “vacía”.

La potencia de este enfoque es que si se sabe que una variable puede llegar a no tener valor en algún momento de su ciclo de vida, debemos escribir código adicional preguntando si ese valor existe o no.

Esto da la ventaja de estar seguros en todo momento sobre qué hacer en cada caso.

Si no existieran los opcionales, puede darse el caso de que no contemplemos un escenario en donde el valor no exista y por lo tanto se generen comportamientos extraños en momentos de ejecución.

Veamos un ejemplo:

[code lang=”Swift”]
let colores = [“azul”, “negro”, “verde”, “rojo”, “amarillo”]

let primerColor = colores.first

//primerColor es es String? en lugar de String
[/code]

Primero definimos un array de String en donde guardamos un listado de colores y luego, creamos una constante para obtener el primero color del arreglo usando la propiedad first.

Como puede darse el caso de que el arreglo esté vacío, first no siempre va a devolver un valor; en algunos casos tomará el primer elemento pero en otros indicará que no se encontró un valor. Esto hace que el resultado sea un opcional.

nil

Volviendo al ejemplo anterior, para indicar la ausencia de un valor se usa un valor especial llamado nil.

Por lo tanto, la constante primerColor no se trata de un tipo String sino de un opcional de String, indicado con un signo de pregunta (?) String?.

Si quisiéramos definir una variable como opcional usamos la siguiente forma:

[code lang=”Swift”]
var segundoColor: String? = “negro”
print(“\(segundoColor)”)

//Devuelve:
//Optional(“negro”)

segundoColor = nil
print(“\(segundoColor)”)

//Devuelve:
//nil
[/code]

Como vemos, definimos la variable indicando el tipo de dato y agregando un signo de pregunta para indicar que se trata de un opcional.

La asignación de un valor en ese momento es optativo, ya que de no hacerlo Swift infiere que su valor inicial es nil.

Luego, si imprimimos esa variable usando print vemos que su valor es Optional(“negro”) en lugar de “negro” a secas. Por último, una variable opcional puede volver a valer nil si es necesario.

Nota
Recordemos que las variables no opcionales deben tener un valor al momento de su definición obligatoriamente.

Opcionales – Forced unwrapping

Si sabemos y estamos seguros que en un determinado momento una variable opcional tiene un valor en su interior, podemos usar la técnica de forced unwrapping (desenvoltura forzada) mediante el uso de un signo de exclamación al final del nombre de la variable (!):

[code lang=”Swift”]
var otroColor:String? = “celeste”
print(“\(otroColor!)”)
//Devuelve
//celeste
[/code]

Como sabemos que la variable otroColor contiene un valor, usamos la desenvoltura forzada y accedemos al dato que el mismo encierra. Ya no estamos obteniendo un Optional(“celeste”) sino que directamente obtenemos “celeste”.

Si quisiéramos preguntar primero si esa variable vale nil o no, una posibilidad es usar una sentencia if:

[code lang=”Swift”]
if otroColor != nil {
print(“\(otroColor!)”)
}

//Devuelve
//celeste
[/code]

En este caso, primero nos aseguramos si la variable no vale nil (o sea, si tiene un valor) y luego hacemos el forced unwrapping, debido a que dentro del if estamos seguros que esa variable tiene un valor.

Para más información sobre el uso del if u otras técnicas condicionales, puedes consultar el capitulo de control de flujo.

Opcionales – Optional Binding

Si bien el enfoque anterior es perfectamente válido, no termina siendo muy cómodo si dentro del if se necesita usar muchas veces el valor dentro del opcional, ya que tendríamos que recordar de usar el ! en todos los casos.

Otra manera de hacer lo mismo pero un poco más eficiente es usando el optional binding (enlace opcional), en el cual se pregunta si un opcional contiene un valor y en caso afirmativo, se crea una constante o variable temporal para ser usada dentro del if o while como si se tratara de una versión no opcional. Una vez fuera de ese if o while, la misma deja de existir.

[code lang=”Swift”]
if let colorElegido = otroColor {
print(“El color elegido es \(colorElegido)”)
}

//Devuelve:
//El color elegido es celeste
[/code]

Como vemos, creamos una constante temporal llamada colorElegido que contiene el valor del opcional otroColor. Dentro del if usamos ese valor y ya fuera de él la constante ya no existe más.

Asimismo, podemos anidar varios enlaces opcionales e incluso usar otras comparaciones convencionales que devuelven true o false. Como resultado, se ingresará al if siempre que todos los opcionales tengan un valor y todas las condiciones devuelvan true. Si alguno de estos casos no se da, el resultado final será false y no se ingresará al if.

[code lang=”Swift”]
let numeroPar = 30

if let colorElegido = otroColor, let numero = Int(“45”), numeroPar < 50 {
print(“El color elegido es \(colorElegido) y el \(numeroPar) es menor a 50”)
print(“\(numero)”)
}

//Devuelve:
//El color elegido es celeste y el 30 es menor a 50
//45
[/code]

Opcionales – implicity unwrapped optionals

En muchas situaciones contamos con variables opcionales que en un momento determinado del programa son inicializados con un valor y que luego no van a volver a valer nil nunca más. Por tal motivo, es conveniente no tener que preguntar a cada instante si esa variable contiene un valor y hacer la desenvoltura correspondiente, ya que se asume que siempre se va a contener un valor.

Un ejemplo muy claro de este escenario se da cuando se programa para un sistema operativo como iOS, en donde los botones que vemos en pantalla al inicio de su ciclo de vida se crean en nil pero luego mientras se inicializan determinados componentes, las variables que hacen referencia a esos botones contienen valor, hasta tanto el usuario cambia de pantalla y se pierden esas referencias. Como en un momento de su ciclo de vida necesitó valer nil, es obligatorio usar un opcional, pero como luego siempre va a tener un valor, no es cómodo tener que usar un if let en cada uso. Para este tipo de casos, los implicity unwrapped optionals (opcionales desenvueltos en forma implícita) son ideales.

Para hacer uso de ellos, en lugar de declarar al opcional usando un signo de pregunta (?) se utiliza un signo de exclamación (!):

[code lang=”Swift”]
let posibleValor:String? = “Estamos aprendiendo Swift! Yeahh!”
let valorSeguro:String! = posibleValor!

print(posibleValor)
print(valorSeguro)

//Devuelve:
//Optional(“Estamos aprendiendo Swift! Yeahh!”)
//Estamos aprendiendo Swift! Yeahh!
[/code]

Podríamos decir que este tipo de opcionales tienen realizan la desenvoltura de su valor en forma automática, sin necesidad de hacerlo explícito. Tengamos en cuenta que si una variable o constante vale nil e intentamos usar su valor, vamos a tener un error en tiempos de ejecución. Por lo tanto, hay que ser muy cuidadoso y pensar bien antes de usar este tipo de opcionales.

En el caso de que exista alguna posibilidad de que una variable se vuelva nil en algún momento del código, entonces no debemos usar este opcional implícito.

Por otro lado, si necesitamos preguntar si ese opcional implícito tiene un valor en su interior o si quisiéramos usar el optional binding, podemos hacerlo:

[code lang=”Swift”]
if valorSeguro != nil {
print(valorSeguro)
}

if let dato = valorSeguro {
print(dato)
}
[/code]

Opcionales – nil-coalescing operator

Este operador, que podríamos traducirlo como operador que viene junto a nil, devuelve el valor que contiene un opcional haciendo una desenvoltura (a) y en caso de encontrar nil, devuelve otro valor (b). Su sintaxis es a ?? b.

Un punto a tener en cuenta es que tanto a como b deben ser del mismo tipo.

[code lang=”Swift”]
var animal:String? = “Perro”

let mascota = animal ?? “Gato”

print(“\(mascota)”)

//Devuelve Perro
[/code]

En el ejemplo, definimos una variable opcional animal con el valor Perro. Al ser opcional, podría no contener un valor (ser nil). Luego se declara una constante mascota que va a valer Perro siempre que la variable animal tenga ese valor. En caso de ser nil, se incluye un valor adicional Gato.

Este operador es muy útil cuando queremos dar un valor por defecto en caso de que una variable o contante no tengan valor.

Descargar archivo playground.
Siguiente capítulo: Funciones.

Name
Email
Review Title
Rating
Review Content
Opcionales en Swift
4,7 rating based on 12.345 ratings
Overall rating: 4.7 out of 5 based on 8 reviews.

Funciones en Swift

Funciones Swift

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.

Funciones – Definición

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 func seguida del nombre de la misma. La forma más básica sería:

[code lang=”Swift”]
func saludar() {
print(“Hola mundo!”)
}

saludar()

//Devuelve:
//Hola mundo!
[/code]

Funciones – 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.

[code lang=”Swift”]
//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!
[/code]

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 String, Int, 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).

Nota
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:

[code lang=”Swift”]
//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!
[/code]

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:

[code lang=”Swift”]
//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
[/code]

Etiquetas de argumento

Otra elemento que podemos usar cuando definimos nuestros parámetros es 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.

[code lang=”Swift”]
//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!
[/code]

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 (_):

[code lang=”Swift”]
//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
[/code]

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.

[code lang=”Swift”]
//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
[/code]

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.

[code lang=”Swift”]
//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
[/code]

Nota
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 inout

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 intencional 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

[code lang=”Swift”]
//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
[/code]

Información adicional
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.

Funciones – Tipo de una función

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

[code lang=”Swift”]
func saludar() {
print(“Hola mundo!”)
}
[/code]

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”

[code lang=”Swift”]
func saludarA(nombre: String) -> String {
return “Hola \(nombre)!”
}
[/code]

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:

[code lang=”Swift”]
//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
[/code]

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.

[code lang=”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
[/code]

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.

Descargar archivo playground.
Siguiente capítulo: Cerraduras.

Name
Email
Review Title
Rating
Review Content
Funciones en Swift
5,0 rating based on 12.345 ratings
Overall rating: 5 out of 5 based on 5 reviews.

Control de flujo en Swift

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.

Control de flujo 1 – 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.

[code lang=”Swift”]
//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
[/code]

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:

[code lang=”Swift”]
for numero in 1…4 {
print(numero)
}

//Devuelve
//1
//2
//3
//4
[/code]

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 “Gabriel” y “Vecino” 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:)

[code lang=”Swift”]
for numero in stride(from: 0, through: 10, by: 2) {
print(numero)
}

//Devuelve:
//0
//2
//4
//6
//8
//10
[/code]

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

[code lang=”Swift”]
for numero in stride(from: 0, to: 10, by: 2) {
print(numero)
}
[/code]

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:

[code lang=”Swift”]
while condición {
sentencias
}
[/code]

[code lang=”Swift”]
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
[/code]

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:

[code lang=”Swift”]
repeat {
sentencias
} while condición
[/code]

[code lang=”Swift”]
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
[/code]

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.

Control de flujo 2 -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.

[code lang=”Swift”]
let promedio = 9

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

//Devuelve:
//Curso aprobado!
[/code]

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.

[code lang=”Swift”]
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!
[/code]

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:

[code lang=”Swift”]
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
}
[/code]

Como se puede observar, primero se define un valor determinado que luego se lo va a comparar con otros valores, dentro de cada case. La potencia que mencionamos anteriormente es que en cada case no solamente se pueden poner 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.

[code lang=”Swift”]
let personajeAnime = “Goku”

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

//Devuelve:
//Kamehameha!!
[/code]

En el ejemplo, se crea un personaje de Animé y se pregunta en cada case por 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:

[code lang=”Swift”]
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
[/code]

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:

[code lang=”Swift”]
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!!
[/code]

Como vemos, fallthrough hace que al terminar de ejecutar ese case siga 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:

[code lang=”Swift”]
let nota = 5

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

//Devuelve:
//Examen desaprobado
[/code]

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.

[code lang=”Swift”]
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
[/code]

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:

[code lang=”Swift”]
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
[/code]

Descarga el archivo playground.
Siguiente capítulo: Opcionales.

Name
Email
Review Title
Rating
Review Content
Control de flujo en Swift
4,9 rating based on 12.345 ratings
Overall rating: 4.9 out of 5 based on 11 reviews.