iOS desde cero con Swift: más rápido en pocas palabras
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
Si bien este tutorial se centra principalmente en las funciones y clausura, tomamos un breve desvío al final para explorar los protocolos y el control de acceso. Trabajaremos con protocolos más adelante en esta serie, por lo que es importante familiarizarse con ellos desde el principio.
Hace unos meses, escribí una serie sobre el lenguaje de programación Swift en el que analicé de cerca funciones y clausura. Este artículo resume lo que escribí en estos tutoriales. Si desea obtener más información sobre funciones y cierres, le recomiendo leer los artículos de esa serie:
Antes de sumergirnos en el tema de las funciones y los cierres, es hora de encender Xcode y crear uno. ¿Listo? Vamos a escribir código.
1. Funciones
¿Qué es una función?
Una función no es más que un bloque de código que se
puede ejecutar cuando sea necesario. Me gustaría comenzar con un
ejemplo para analizar la anatomía básica de una función en Swift. Agregue la siguiente definición de función a su patio de juegos.
1 |
func printHelloWorld() { |
2 |
print("Hello World!") |
3 |
}
|
Una función comienza con la palabra clave func y es seguida por el nombre de la función, printHelloWorld en el ejemplo anterior. Como
en muchos otros idiomas, el nombre de la función es seguido por un par
de paréntesis que contienen los parámetros de la función, la entrada de
la función.
El cuerpo de la función está envuelto en un par de llaves. El
cuerpo de la función printHelloWorld() contiene una declaración en la
que imprimimos la cadena "Hello World!" en la salida estándar. Así es
como se ve una función básica en Swift. Para invocar la función,
escribimos su nombre seguido de un par de paréntesis.
1 |
printHelloWorld() |
Parámetros
Hagamos que el ejemplo anterior sea un poco más complejo agregando
parámetros a la definición de la función. Al agregar parámetros,
proporcionamos a la función valores de entrada que puede usar en su
cuerpo. En el siguiente ejemplo, definimos la función printMessage(_
:), que acepta un parámetro, message, de tipo String.
1 |
func printMessage(message: String) { |
2 |
print(message) |
3 |
}
|
Una función puede aceptar múltiples parámetros o valores de entrada. Los parámetros están envueltos por los paréntesis que siguen el nombre
de la función. El nombre del parámetro es seguido por dos puntos y su
tipo. Como recuerdas, esto es muy similar a declarar una variable o
constante. Simplemente dice que el parámetro message es de tipo
String.
Invocar la función es similar a lo que vimos anteriormente. La
diferencia es que pasamos en un argumento.
1 |
printMessage("Hello World!") |
El siguiente ejemplo se ve similar. La única diferencia es que la función define dos parámetros, message de tipo String y times de tipo Int.
1 |
func printMessage(message: String, times: Int) { |
2 |
for i in 0..<times { |
3 |
print("\(i) \(message)") |
4 |
}
|
5 |
}
|
Si bien el nombre de la función es idéntico al de la función printMessage(_ :) original, el tipo de función es diferente. Es importante que entiendas esta diferencia. Cada función tiene un tipo, que consiste en los tipos de parámetros y el
tipo de retorno. Exploraremos los tipos de devolución en un momento. Las
funciones pueden tener el mismo nombre siempre que su tipo sea
diferente, como se muestra en las dos definiciones de funciones
anteriores.
El tipo de la primera función es (String) -> () mientras
que el tipo de la segunda función es (String, Int) -> (). El nombre
de ambas funciones es el mismo. No te preocupes por el símbolo ->. Su
significado quedará claro en unos momentos cuando analicemos los tipos en retorno.
La segunda función printMessage(_:times:) define dos
parámetros, message de tipo String y times de tipo Int. Esta
firma de función ilustra una de las características que Swift ha
adoptado de Objective-C, función legible y nombres de métodos.
1 |
printMessage("Hello World!", times: 3) |
Tipos en devolución
Las funciones que hemos visto hasta ahora no nos devuelven nada cuando
las invocamos. Vamos a crear una función que formatea una fecha. La
función toma dos argumentos, una fecha de tipo NSDate y una cadena de
formato de tipo String. Como
las clases NSDate y NSDateFormatter están definidas en el marco Foundation, necesitamos importar Foundation en la parte superior.
1 |
import Foundation |
2 |
|
3 |
func formatDate(date: NSDate, format: String = "YY/MM/dd") -> String { |
4 |
let dateFormatter = NSDateFormatter() |
5 |
dateFormatter.dateFormat = format |
6 |
return dateFormatter.stringFromDate(date) |
7 |
}
|
Hay algunas cosas que requieren alguna explicación. El símbolo -> indica que la función devuelve un valor. El tipo del
valor se define después del simbolo ->, String.
La función acepta dos
argumentos y el segundo tiene un valor predeterminado. Esto se indica
mediante la asignación que sigue al tipo del segundo argumento. El valor
predeterminado de format es "YY/MM/dd". Esto significa que
podemos invocar la función con un solo argumento. La autocompletación de
Xcode ilustra esto.



Si una función no tiene un tipo de retorno, el
símbolo -> se omite. Es por eso que la función printHelloWorld() no
tiene un símbolo -> en su definición de método.
Nombres de parámetros locales y externos
Anteriormente en este tutorial, definimos la función
printMessage(_:). Aunque le hemos dado un nombre al parámetro,
message, no usamos ese nombre cuando llamamos a la función. En cambio,
solo pasamos el valor para el parámetro de message.
1 |
func printMessage(message: String) { |
2 |
print(message) |
3 |
}
|
4 |
|
5 |
printMessage("Hello World!") |
El nombre que definimos en la definición de la función es un nombre de parámetro local. El valor del parámetro solo puede referenciarse con este nombre en el cuerpo de la función. Pero los parámetros de función son un poco más flexibles que eso.
Objective-C es conocido por sus largos nombres de método. Si bien esto puede parecer torpe y poco elegante para los de afuera, hace que los métodos sean fáciles de entender y, si se los elige bien, son muy descriptivos. Si crees que perdiste este beneficio al cambiar a Swift, entonces te sorprenderas.
Cuando una función acepta varios
parámetros, no siempre es obvio qué argumento corresponde a qué
parámetro. Eche un vistazo al siguiente ejemplo para comprender mejor el
problema.
1 |
func power(a: Int, b: Int) -> Int { |
2 |
var result = a |
3 |
|
4 |
for _ in 1..<b { |
5 |
result = result * a |
6 |
}
|
7 |
|
8 |
return result |
9 |
}
|
10 |
|
11 |
power(2, 3) |
La función power(_:b:) aumenta el valor de a por el exponente b. Ambos parámetros son de tipo Int. Si
bien la mayoría de las personas pasará intuitivamente el valor base
como primer argumento y el exponente como segundo argumento, esto no
queda claro a partir del tipo, nombre o firma de la función.
Para evitar
confusiones, podemos dar los parámetros de una función nombres externos. Luego
podemos usar estos nombres externos cuando se llama a la función para
indicar inequívocamente qué argumento corresponde a qué parámetro. Echa
un vistazo al ejemplo actualizado a continuación.
1 |
func power(base a: Int, exponent b: Int) -> Int { |
2 |
var result = a |
3 |
|
4 |
for _ in 1..<b { |
5 |
result = result * a |
6 |
}
|
7 |
|
8 |
return result |
9 |
}
|
Tenga en cuenta que el cuerpo de la función no ha cambiado, porque los nombres locales no se han cambiado. Sin embargo, cuando invocamos la función actualizada, la diferencia es clara y el resultado es mucho menos confuso.
1 |
power(base: 2, exponent: 3) |
Aunque los tipos de ambas funciones son las mismas, (Int, Int) -> Int, las funciones son diferentes. En otras palabras, la segunda función no es una redeclaración de la
primera función. La sintaxis para invocar la segunda función puede
recordarles a algunos de ustedes el Objetivo-C. No
solo se describen claramente los argumentos, la combinación de nombres
de funciones y parámetros describe el propósito de la función.
En Swift,
el primer parámetro tiene, por defecto, ningún nombre de parámetro
externo. Es por eso que la firma de la función printMessage(_:) incluye un _ para el primer parámetro. Si
queremos definir un nombre de parámetro externo para el primer
parámetro, la definición del método se vería un poco diferente.
1 |
func printMessage(message message: String) { |
2 |
print(message) |
3 |
}
|
4 |
|
5 |
printMessage(message: "Hello World!") |
Funciones globales y anidadas
Los ejemplos que hemos visto hasta ahora se conocen como funciones globales, porque están definidos en un ámbito global. Las funciones también se pueden anidar. Las funciones anidadas solo pueden invocarse desde el ámbito en el que están definidas. Esto significa que una función anidada solo puede invocarse desde la función en la que está definida. El siguiente ejemplo aclara esto.
1 |
func printMessage(message: String) { |
2 |
let a = "hello world" |
3 |
|
4 |
func printHelloWorld() { |
5 |
print(a) |
6 |
}
|
7 |
}
|
2. Clausuras
¿Qué es una clausura?
Si has trabajado con bloques en C y Objective-C o clausuras en JavaScript, entonces no tendrás problemas para enfrentarte al concepto de clausuras. Ya hemos trabajado con clausuras en este artículo, porque las funciones también son clausuras. Comencemos con lo básico e inspeccionemos la anatomía de una clausura.
Una clausura es un bloque de funcionalidad que puede pasar en su código. Puede pasar una clausura como argumento de una función o puede almacenarlo como propiedad de un objeto. Las clasuruas tienen muchos casos de uso.
El nombre Clausura hace alusión a una de las características clave de las clausuras. Una clausura captura las variables y constantes del contexto en el que está definido. Esto a veces se denomina como clausuras de esas variables y constantes.
Sintaxis
La sintaxis
básica de un cierre no es difícil. Eche un vistazo al siguiente ejemplo.
1 |
let a = {(a: Int) -> Int in |
2 |
return a + 1 |
3 |
}
|
Lo primero que notará es que todo la clausura está envuelta en un par de llaves. Los parámetros de la clausura están envueltos en un par de paréntesis,
separados del tipo de retorno por el símbolo ->. La clausura anterior
acepta un argumento, a, de tipo Int y devuelve un Int. El cuerpo comienza después de la palabra clave in.
Las clausuras nombrados, es decir, las funciones globales y anidadas, se ven un poco diferentes. El siguiente ejemplo ilustra estas diferencias.
1 |
func increment(a: Int) -> Int { |
2 |
return a + 1 |
3 |
}
|
Las diferencias más destacadas son el uso de la palabra clave func y la posición de los parámetros y el tipo del retorno. Una clausura comienza y termina con una llave, envolviendo los parámetros,
el tipo de retorno y el cuerpo. A pesar de estas diferencias, recuerde
que cada función es una clausura. Sin embargo, no todos son una
función.
Clausuras como parámetros
Las clausuras son potentes y el siguiente
ejemplo ilustra lo útiles que pueden ser. En el ejemplo, creamos una
matriz de estados. Invocamos
la función map() en la matriz para crear una nueva matriz que solo
contiene las dos primeras letras de cada estado como una cadena en
mayúscula.
1 |
var states = ["California", "New York", "Texas", "Alaska"] |
2 |
|
3 |
let abbreviatedStates = states.map({ (state: String) -> String in |
4 |
let index = state.startIndex.advancedBy(2) |
5 |
return state.substringToIndex(index).uppercaseString |
6 |
})
|
7 |
|
8 |
print(abbreviatedStates) |
En el
ejemplo anterior, la función map() se invoca en la matriz states ,
transforma sus contenidos y devuelve una nueva matriz que contiene los
valores transformados. El ejemplo también muestra el poder de la inferencia de tipos de Swift. Como invocamos la función map() en una matriz de cadenas, Swift sabe
que el argumento de state es de tipo String. Esto significa que podemos
omitir el tipo como se muestra en el ejemplo actualizado a
continuación.
1 |
let abbreviations = states.map({ (state) -> String in |
2 |
return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString |
3 |
})
|
Hay algunas cosas más que podemos omitir del ejemplo anterior, lo que da como resultado el siguiente de una linea.
1 |
let abbreviations = states.map({ state in state.substringToIndex(state.startIndex.advancedBy(2)).uppercaseString }) |
Déjame explicarte lo que está sucediendo. El
compilador puede inferir que devolvemos una cadena del cierre que
pasamos a la función map(), lo que significa que no hay razón para
incluir el tipo de devolución en la definición de expresión de clausura. Sin embargo, solo podemos hacer esto si el cuerpo de la clausura incluye una
sola declaración. En
ese caso, podemos poner esa declaración en la misma línea que la
definición de la clausura, como se muestra en el ejemplo anterior. Como no
hay ningún tipo de devolución en la definición y ningún
símbolo -> que preceda al tipo de devolución, podemos omitir los
paréntesis que encierran los parámetros de la clausura.
Nombres de argumentos abreviados
Sin embargo, no se detiene aquí. Podemos utilizar nombres de
argumentos abreviados para simplificar aún más la expresión de clausura anterior. Al usar una expresión de clausura en línea, como en el ejemplo
anterior,
podemos omitir la lista de parámetros, incluida la palabra clave in que
separa los parámetros del cuerpo de clausura.
En el cuerpo de cierre,
hacemos referencia a los argumentos usando nombres de argumentos
abreviados que nos proporciona Swift. El primer argumento es
referenciado por $0, el segundo por $1, etc.
En
el ejemplo actualizado a continuación, he omitido la lista de
parámetros y la palabra clave in, y reemplacé el argumento state en
el cuerpo de clausura con el nombre de argumento abreviado $0. La
declaración resultante es más conciso sin comprometer la legibilidad.
1 |
let abbreviations = states.map({ $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString }) |
Clausuras que se arrastran
El lenguaje de programación Swift también define un concepto conocido como clausuras finales. Si pasa una clausura como el último argumento de una función, puede colocar esa clausura fuera del paréntesis de la llamada a la función. El siguiente ejemplo demuestra cómo funciona esto.
1 |
let abbreviations = states.map() { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString } |
Si el único argumento de la llamada a la función es el cierre, entonces incluso es posible omitir los paréntesis de la llamada a la función.
1 |
let abbreviations = states.map { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString } |
Tenga en cuenta que esto también funciona para clausuras que contienen declaraciones múltiples. De hecho, esa es la razón principal por la que las clausuras finales están disponibles en Swift. Si una clausura es larga o compleja y es el último argumento de una función, a menudo es mejor usar la sintaxis de clausura final.
1 |
let abbreviations = states.map { (state: String) -> String in |
2 |
let toIndex = state.startIndex.advancedBy(2) |
3 |
let newState = state.substringToIndex(toIndex) |
4 |
return newState.uppercaseString |
5 |
}
|
3. Protocolos
Los protocolos son un componente importante del lenguaje de programación Swift. El concepto no es difícil de entender si está familiarizado con los protocolos en Objective-C o las interfaces en Java. Un protocolo define un diseño o interfaz enfocado en una tarea particular. Lo hace al definir las propiedades y los métodos necesarios para realizar esa tarea.
Definir un protocolo es similar a definir una clase o estructura. En el siguiente ejemplo, definimos el protocolo Repairable. El protocolo
define dos propiedades, time y cost, y un método repair(). La propiedad time es de solo lectura mientras que la propiedad cost es Lectura-Escritura.
1 |
protocol Repairable { |
2 |
var time: Float { get } |
3 |
var cost: Float { get set } |
4 |
|
5 |
func repair() |
6 |
}
|
Una clase o estructura puede adoptar un protocolo conformándose a él. Esto significa que se requiere implementar las propiedades y métodos
definidos por el protocolo. Actualicemos la clase Boat que
implementamos en el tutorial anterior.
1 |
class Boat: Repairable { |
2 |
|
3 |
var speed: Float = 0 |
4 |
var lifeboats: Int = 2 |
5 |
|
6 |
var time: Float = 10.0 |
7 |
var cost: Float = 100.0 |
8 |
|
9 |
func deployLifeboats() { |
10 |
// ...
|
11 |
}
|
12 |
|
13 |
func repair() { |
14 |
// ...
|
15 |
}
|
16 |
|
17 |
}
|
Así de fácil es adaptar un tipo a un protocolo. Aunque un protocolo no se puede instanciar como una clase o estructura,
un protocolo es un tipo válido. Observe el siguiente ejemplo en el que
el protocolo Repairable se usa como el tipo de argumento de una función.
1 |
func bringInForRepairs(toBeRepaired: Repairable) { |
2 |
// ...
|
3 |
}
|
4 |
|
5 |
let myDamagedBoat = Boat() |
6 |
|
7 |
bringInForRepairs(myDamagedBoat) |
Davis Allie escribió recientemente un excelente artículo sobre programación orientada a protocolos en Swift. Si está interesado en aprender más acerca de los protocolos y su potencial en Swift, entonces le sugiero que lea el artículo de Davis.
4. Control de acceso
Me gustaría concluir esta introducción a Swift hablando de control de acceso. Como su nombre lo indica, el control de acceso define qué código tiene acceso a qué código. Los niveles de control de acceso se aplican a métodos, funciones, tipos, etc. Apple simplemente se refiere a las entidades. Hay tres niveles de control de acceso, público, interno y privado.
- Público: las entidades definidas en el mismo módulo y otros módulos, como un proyecto, un marco o una biblioteca, pueden acceder a las entidades marcadas como públicas. Este nivel de control de acceso es ideal para exponer la interfaz de un marco.
- Interno: este es el nivel de control de acceso predeterminado. En otras palabras, si no se especifica un nivel de control de acceso, se aplica el nivel de control de acceso interno. Solo las entidades definidas en el mismo módulo pueden acceder a una entidad con un nivel de acceso interno.
- Privado: una entidad declarada como privada solo es accesible para las entidades definidas en el mismo archivo fuente.
Eche un vistazo al siguiente ejemplo en el que
he actualizado la clase Boat. La clase en sí está marcada como pública,
lo que significa que es accesible desde cualquier lugar. La propiedad cost se marca implícitamente como interna porque no hemos especificado
un nivel de control de acceso. Lo mismo es cierto para el método
deployLifeboats().
1 |
public class Boat { |
2 |
|
3 |
public var speed: Float = 0 |
4 |
public var lifeboats: Int = 2 |
5 |
|
6 |
var cost: UInt = 100 |
7 |
|
8 |
func deployLifeboats() { |
9 |
// ...
|
10 |
}
|
11 |
|
12 |
private func scheduleMaintenance() { |
13 |
|
14 |
}
|
15 |
}
|
El
método scheduleMaintenance() está marcado como privado, lo que
significa que solo puede ser invocado por las entidades definidas en el
archivo fuente en el que se define la clase Boat. Es importante entender esto, porque es ligeramente diferente de lo que
otros lenguajes de programación consideran un método o propiedad
privada.
Si marcamos la clase Boat como interna al eliminar la palabra
clave public, el compilador nos mostrará una advertencia. Nos dice que
no podemos marcar speed y que lifeboats son públicos
siempre que Boat se marque como interno. El compilador tiene razón, por
supuesto. No tiene sentido marcar las propiedades de una clase interna
como pública.



Conclusión
El lenguaje de programación Swift es fácil de aprender, pero hay mucho más que lo que hemos cubierto en los últimos dos tutoriales. Aprenderá más sobre Swift una vez que comencemos a crear aplicaciones. En el siguiente artículo, echamos un vistazo más de cerca al iOS SDK.
Si tiene preguntas
o comentarios, puede dejarlos en los comentarios a continuación o
comunicarse conmigo en Twitter.



