Los closures son funciones anónimas que se pueden guardar en una variable.
Has leído bien: funciones que se pueden guardar en variables. Pero también pueden pasarse como parámetro de otra función, utilizarse como tipo de retorno de una función o añadirlos a una colección.
En Swift, las funciones son ciudadanos de «primera clase». Esto quiere decir que son como cualquier otro tipo más, por ejemplo Int
, String
, Double
, o los tipos que te crees mediante classes, structs, enums o protocols.
La sintaxis de un closure es:
{ (param1: OneType, param2: AnotherType) -> ReturnType in
// code
}
- Un closure está delimitado por llaves:
{ }
- Entre paréntesis, el nombre de los parámetros de entrada junto con su tipo, separados por comas. Exactamente igual que en las funciones.
-> ReturnType
: El tipo de dato que devuelve el closurein
: keyword del lenguaje que indica el comienzo del cuerpo del closure
¿Los closures son funciones?
En realidad, una función en Swift es un closure pero con azúcar sintáctico (o syntatic lugar, en inglés). Es decir, con una sintaxis más bonita y amigable para el desarrollador.
Para comprobarlo, abre un Playground y escribe lo siguiente:
func add42(_ number: Int) -> Int {
return number + 42
}
let add42Closure = { (number: Int) -> Int in
return number + 42
}
add42(8) // 50
add42Closure(8) // 50
Tanto la función add42
como el closure add42Closure
son equivalentes. De echo, el compilador de Swift transforma las funciones en closures de esta manera.
Por ejemplo, la función add42
es traducida por el compilador como:
let add42 = { (number: Int) -> Int in
return number + 42
}
El nombre de la función es el nombre que Swift le da a la variable donde se almacena el closure.
Tipo de un Closure
Al tratarse de «ciudadanos de primera clase», los closures se pueden utilizar como cualquier otro tipo. El tipo de un closure se define así:
(TypeOfFirstParam, TypeOfSecondParam, ...) -> ReturnType
Por ejemplo:
- Una función que acepta un parámetro de tipo entero, y devuelve un entero:
(Int) -> Int
- Una función que no acepta parámetros y devuelve una cadena:
() -> String
- Una función que acepta un primer parámetro de tipo cadena y un segundo de tipo entero, y no retorna nada:
(String, Int) -> Void
- Y así sucesivamente…
Closures como parámetro de entrada de una función:
Ya que son tipos como cualquier otro, podemos usar closures como tipo de un parámetro de entrada de una función:
func add42(_ number: Int) -> Int {
return number + 42
}
func add10(_ number: Int) -> Int {
return number + 10
}
func apply(
_ function: (Int) -> Int, // <-- Closure como parámetro
with number: Int
) -> Int {
return function(number)
}
// 1
apply(add42, with: 8) // 50
// 2
apply(add10, with: 8) // 18
La función apply
simplemente aplica la función function
que le entra como parámetro con el parámetro number
de tipo Int
.
// 1
: se aplica la funciónadd42
con el número 8, siendo el resultado 50.// 2
: se aplica la funciónadd10
con el número 8, siendo el resultado 18.
Closures como tipos de retorno de otra función
Los closures también pueden usarse como tipo de retorno de una función.
Vamos a crear una función que es capaz de crear otras funciones que suman un número. Dicho de otro modo, vamos a crear una función que sea capaz de crear las funciones add42
o add10
del ejemplo anterior:
func adder(number: Int) -> (Int) -> Int {
// 1
func add(x: Int) -> Int {
return x + number
}
// 2
return add
}
// 1
: ¿Una función dentro de otra función? Sí, es posible hacerlo en Swift. la funciónadd
sólo existe en el ámbito de la funciónadder
. Es de tipo(Int) -> Int
, justo el tipo de retorno de la funciónadder
.// 2
: Dado que la funciónadd
es de tipo(Int) -> Int
y eso es lo que devuelve la funciónadder
, simplemente devolvemos la funciónadd
Con esto hemos creado una función que puede crear una función que añade el número que nosotros queramos:
// Creamos funciones "añadidoras"
let add42 = adder(number: 42)
let add10 = adder(number: 10)
// Utilizamos las funciones recién creadas
add42(8) // 50
add10(8) // 18
Closures dentro de colecciones
Al igual que cualquier otro tipo, podemos crear un array de closures (Int) -> Int
:
let functionList: [(Int) -> Int] = [add42, add42Closure, add10]
Y por supuesto, podemos iterar sobre él:
for function in functionList {
function(8)
}
Sintaxis abreviadas de los closures
Swift infiere los tipos. Esto quiere decir que en muchas ocasiones, el compilador es capaz de saber el tipo de datos que hay sin necesidad de escribirlo explícitamente:
let name = "Alexandre" // El compilador sabe que es de tipo String
Por eso, es muy habitual utilizar la sintaxis reducida en los closures, ya que Swift es capaz de adivinarlo por sí solo si tiene suficiente información:
let functionList = [
add42, // En este momento, Swift ya sabe que es un array [(Int) -> Int]
{ (number: Int) -> Int in return number + 42 }, // 1
{ (number: Int) in return number * 2 }, // 2
{ number in return number + 8 } // 3
{ number in number + 42 } // 4
{ $0 + 100 } // 5
]
// 1
: Esta es la sintaxis 100% explícita.// 2
: Swift puede inferir que el tipo de retorno esInt
, así que se puede omitir.// 3
: Swift también puede inferir que el parámetro de entrada esInt
, así que también se puede omitir// 4
: Cuando el cuerpo de la función sólo tiene una sentencia, se puede omitir la palabrareturn
// 5
: No es necesario ni siquiera darle un nombre a los parámetros de entrada.$0
,$1
,$2
, etc… serían el primer, segundo y tercer parámetro de entrada respectivamente, de tener más de uno.
Más sobre closures:
- @escaping vs @nonescaping closures en Swift¿Sabes cuál es la diferencia entre los @escaping y los @nonescaping closures en Swift? ¿Sabrías explicarlo? En este artículo te cuento cuáles son las diferencias entre @escaping y @nonescaping en Swift y por qué es importante para el lenguaje hacer esta distinción. Todo ello utilizando ejemplos que ayuden a comprender las diferencias.
- Referencias circulares en Closures¿Sabías que si estás utilizando closures podrías estar plagando tu aplicación de fugas de memoria? En este artículo entenderás por qué ocurre y cómo puedes evitarlo.
Comparte esta página
Si te ha resultado útil, comparte esta página en tus redes preferidas.
Únete a la newsletter para no perderte ningún nuevo tutorial: recibe un email cuando se publiquen nuevos artículos o vídeos y no pierdas la oportunidad de seguir aprendiendo.