Skip to content

¿Qué es un closure en Swift?

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 closure
  • in: 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ón add42 con el número 8, siendo el resultado 50.
  • // 2: se aplica la función add10 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ón add sólo existe en el ámbito de la función adder. Es de tipo (Int) -> Int, justo el tipo de retorno de la función adder.
  • // 2: Dado que la función add es de tipo (Int) -> Int y eso es lo que devuelve la función adder, simplemente devolvemos la función add

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 es Int, así que se puede omitir.
  • // 3: Swift también puede inferir que el parámetro de entrada es Int, así que también se puede omitir
  • // 4: Cuando el cuerpo de la función sólo tiene una sentencia, se puede omitir la palabra return
  • // 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.