Advertisement
Scroll to top
Read Time: 9 min
This post is part of a series called Swift From Scratch.
Swift From Scratch: Function Parameters, Types, and Nesting
Swift From Scratch: An Introduction to Classes and Structures

() translation by (you can also view the original English article)

Si has trabajado con bloques en C/Objective-C o lambdas en Ruby, entonces no te costará trabajo entender el concepto de cierres.  Los cierres no son más que bloques de funcionalidad que puedes pasar alrededor en tu código. 

De hecho, ya trabajamos con cierres en los dos artículos anteriores.  Eso es correcto.  Las funciones son cierres también.  Comencemos con los básicos e inspeccionemos la anatomía de un cierre. 

1. ¿Qué Es un Cierre? 

Como dije, un cierre es un bloque de funcionalidad que puedes pasar alrededor de tu código. Puedes pasar un cierre como un argumento a una función o puedes almacenarlo como una propiedad de un objeto. Los cierres tienen muchos casos de uso.

El nombre cierre da una pista de las características clave de los cierres. Un cierre captura las variables y constantes del contexto en el cuál es definido. Esto es a veces, referido como cerrar sobre esas variables y constantes. Vamos a ver la captura de valores a más detalle al final de este artículo.

Flexibilidad

Ya has aprendido que las funciones pueden ser increíblemente poderosas y flexibles. Debido a que las funciones son cierres, los cierres son igual de flexibles. En este artículo, descubrirás qué tan flexibles y poderosas son.

Administración de Memoria

El lenguaje de programación C tiene un concepto similar, bloques. Los cierres en Swift, sin embargo, tienen unos cuantos beneficios. Una de las ventajas clave de los cierres en Swift es que la memoria es algo sobre lo que tu, el desarrollador, no tiene que preocuparse.

Incluso los ciclos de retención, que no son poco comunes en C/Objective-C, son manejados por Swift. Esto reducirá fugas de memoria difíciles de encontrar o fallas debido a apuntadores inválidos.

2. Sintaxis

La sintaxis básica de un cierre no es difícil y te recordará a funciones globales y anidadas, que cubrimos anteriormente en esta serie. Echa un vistazo al siguiente ejemplo.

1
{(a: Int) -> Int in
2
    return a + 1
3
}

La primera cosa que notarás es que el cierre entero está envuelto en un par de llaves. Los parámetros del cierre están envueltos en un par de paréntesis, separados desde el tipo de retorno por el símbolo ->. El cierre de arriba acepta un argumento, a, de tipo Int y devuelve un Int. El cuerpo del cierre comienza después de la palabra clave in.

Los cierres nombrados, eso es, funciones globales y anidadas, se ven un poco diferentes. El siguiente ejemplo debería ilustrar las diferencias.

1
func increment(a: Int) -> Int {
2
    return a + 1
3
}

Las diferencias más prominentes son el uso de la palabra clave func y la posición de los parámetros y tipo de retorno. Un cierre comienza y termina con una llave, envolviendo parámetros, tipo de retorno, y cuerpo de cierre. A pesar de estas diferencias, recuerda que cada función es un cierre. No todo cierre es una función sin embargo.

3. Cierres como Parámetros

Los cierres son poderosos y el siguiente ejemplo ilustra qué tan útiles pueden ser. En el ejemplo, creamos un arreglo de estados. Invocamos la función map sobre el arreglo para crear un nuevo arreglo que contenga las dos primeras letras de cada estado como una cadena capitalizada.

1
var states = ["California", "New York", "Texas", "Alaska"]
2
3
let abbreviatedStates = states.map({ (state: String) -> String in
4
    return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
5
})
6
7
println(abbreviatedStates)

La función o método map es común en lenguajes de programación y librerías comunes, tales como Ruby, PHP, y jQuery. En el ejemplo de arriba, la función map es invocada en el arreglo states, transforma sus contenidos, y devuelve un nuevo arreglo que contiene los valores transformados.

Interfaz de Tipo

Anteriormente en esta serie, aprendimos que Swift es bastante inteligente. Veamos exactamente qué tan inteligente. El arreglo de estados es un arreglo de cadenas. Debido a que invocamos la función map sobre el arreglo, Swift sabe que el argumento state es de tipo String. Esto significa que podemos omitir el tipo como se muestra en el ejemplo actualizado de abajo.

1
let abbreviations = states.map({ (state) -> String in
2
    return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
3
})

Hay algunas cuantas cosas más que podemos omitir del ejemplo de arriba, resultando en la siguiente línea:

1
let abbreviations = states.map({ state in state.substringToIndex(advance(state.startIndex, 2)).uppercaseString })

Déjame explicar qué está sucediendo. El compilador puede inferir que regresamos una cadena desde el cierre que pasamos a la función map, lo que significa que no hay razón para incluirla en la definición de expresión de cierre. Solo podemos hacer esto si el cuerpo del cierre incluye una sola declaración sin embargo. En ese caso, podemos poner esa declaración en la misma línea que la definición del cierre, como se muestra en el ejemplo de arriba. Debido a que no hay tipo de retorno en la definición y sin símbolo -> precediendo al tipo de retorno, podemos omitir el los paréntesis encerrando los parámetros del cierre.

Nombres de Argumentos Shorthand

Aunque esto no termina aquí. Podemos hacer uso de nombres de argumento shorthand para simplificar la expresión de cierre de arriba incluso más. Cuando usamos una expresión de cierre en línea, como en el ejemplo de arriba, podemos omitir la lista de parámetros, incluyendo la palabra clave in que separa los parámetros del cuerpo del cierre.

En el cuerpo del cierre, referenciamos los argumentos usando nombres de argumento shorthand que Swift nos proporciona. El primer argumento es referenciado por $0, el segundo por $1, etc.

En el ejemplo actualizado de abajo, hemos omitido la lista de parámetros y la palabra clave in, y reemplazado el argumento state en el cuerpo del cierre con el nombre de argumento shorthand $0. La declaración resultantes es más concisa sin comprometer legibilidad.

1
let abbreviations = states.map({ $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString })

Rastreando Cierres

El lenguaje de programación Swift también define un concepto conocido como rastrear cierres. La idea es simple. Si pasas un cierre como el último argumento de una función, puedes colocar el cierre fuera de paréntesis de la llamada de función. El siguiente ejemplo demuestra cómo funciona esto.

1
let abbreviations = states.map() { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }

Si el único argumento de la llamada a la función es el cierre, entonces es incluso posible omitir el paréntesis de la llamada de función.

1
let abbreviations = states.map { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }

Nota que esto también funciona para cierres que contienen múltiples declaraciones. De hecho, esa es la razón principal que el rastreo de cierres está disponible en Swift. Si un cierre es largo o complejo, y es el último argumento de una función, es frecuentemente mejor usar la sintaxis de rastreo de cierre.

1
let abbreviations = states.map { (state: String) -> String in
2
    let newState = state.substringToIndex(advance(state.startIndex, 2))
3
    return newState.uppercaseString
4
}

4. Capturando Valores

Cuando se usan cierres, frecuentemente te encontrarás usando o manipulando constantes y variables desde el contexto que rodea al cierre en el cuerpo del cierre. Esto es posible y frecuentemente referido como capturar el valor. Simplemente significa que un cierre puede capturar los valores de constantes y variables del contexto en que es definido. Toma el siguiente ejemplo para entender mejor el concepto de capturar valor.

1
func changeCase(uppercase: Bool, strings: String...) -> [String] {
2
    var newStrings = [String]()
3
    
4
    func changeToUppercase() {
5
        for s in strings {
6
            newStrings.append(s.uppercaseString)
7
        }
8
    }
9
    
10
    func changeToLowerCase() {
11
        for s in strings {
12
            newStrings.append(s.lowercaseString)
13
        }
14
    }
15
    
16
    if uppercase {
17
        changeToUppercase()
18
    } else {
19
        changeToLowerCase()
20
    }
21
    
22
    return newStrings
23
}
24
25
let uppercaseStates = changeCase(true, "Califorina", "New York")
26
let lowercaseStates = changeCase(false, "Califorina", "New York")

Estoy seguro que estás de acuerdo en que el ejemplo de arriba es un poco inventado, pero muestra claramente cómo funciona la captura de valor en Swift. Las funciones anidadas, changeToUppercase y  changeToLowercase, tienen acceso a los argumentos externos de la función, states, así como a la variable newStates declarada en la función externa. Déjame explicar qué sucede.

La función changeCase acepta un booleano como su primer argumento y un parámetro variadico de tipo String como su segundo parámetro. La función devuelve un arreglo de cadenas compuestas de las cadenas pasadas a la función como el segundo parámetro. En el cuerpo de la función, creamos un arreglo mutable de cadenas, newStrings, en el cuál almacenamos las cadenas manipuladas.

Las funciones anidadas ciclan sobre las cadenas que son pasadas a la función changeCase y cambian la mayúscula de cada cadena. Como puedes ver, tienen acceso directo a las cadenas pasadas por la función changeCase así como al arreglo newStrings, que es declarado en el cuerpo de la función changeCase.

Revisamos el valor de uppercase, llamamos a la función apropiada, y devolvemos el arreglo newStrings. Las dos líneas al final del ejemplo demuestran cómo trabaja la función changeCase.

Incluso aunque he demostrado la captura de valor con funciones, recuerda que toda función es un cierre. En otras palabras, las mismas reglas aplican a cierres no nombrados.

Cierres

Ha sido mencionado varias veces en este artículo, las funciones son cierres. Hay tres tipos de cierres:

  • funciones globales
  • funciones anidadas
  • expresiones de cierre

Funciones globales, tales como la función println de la librería estándar de Swift, no captura valores. Las funciones anidadas, sin embargo, tienen acceso a y pueden capturar los valores de constantes y valores de la función en las que están definidas. El ejemplo previo ilustra este concepto.

Las expresiones de cierre, también conocidas como cierres sin nombre, pueden capturar los valores de constantes y variables en el contexto en que son definidas. Esto es muy similar a funciones anidadas.

Copiando y Referenciando

Un cierre que captura el valor de una variable puede cambiar el valor de esa variable. Swift es lo suficientemente inteligente para saber si debería copiar o referenciar los valores de las constantes y variables que captura.

Los desarrolladores que son nuevos en Swift y tienen poca experiencia con otros lenguajes de programación darán este comportamiento por hecho. Sin embargo, es una ventaja importante que Swift entienda cómo están siendo usados los valores capturados en un cierre y, como resultado, puede manejar la administración de memoria por nosotros.

Aprende Más en Nuestro Curso de Programación Swift

Si estás interesado en llevar tu educación Swift al siguiente nivel, puedes echar un vistazo nuestro curso completo sobre desarrollo Swift.

Conclusión

Los cierres son un concepto importante y los usarás frecuentemente en Swift. Te permiten escribir código dinámico y flexible que es sencillo de escribir y entender. En el siguiente artículo, exploraremos la programación orientada a objetos en Swift, comenzando con objetos, estructuras y clases.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.