Swift Language
Cierres
Buscar..
Sintaxis
- var closingVar: (<parameters>) -> (<returnType>) // Como variable o tipo de propiedad
- typealias ClosureType = (<parameters>) -> (<returnType>)
- {[<captureList>] (<parameters>) <throws-ness> -> <returnType> en <statements>} // Completa sintaxis de cierre
Observaciones
Para obtener más información sobre los cierres Swift, consulte la documentación de Apple .
Fundamentos de cierre
Los cierres (también conocidos como bloques o lambdas ) son piezas de código que se pueden almacenar y transmitir dentro de su programa.
let sayHi = { print("Hello") }
// The type of sayHi is "() -> ()", aka "() -> Void"
sayHi() // prints "Hello"
Al igual que otras funciones, los cierres pueden aceptar argumentos y devolver resultados o generar errores :
let addInts = { (x: Int, y: Int) -> Int in
return x + y
}
// The type of addInts is "(Int, Int) -> Int"
let result = addInts(1, 2) // result is 3
let divideInts = { (x: Int, y: Int) throws -> Int in
if y == 0 {
throw MyErrors.DivisionByZero
}
return x / y
}
// The type of divideInts is "(Int, Int) throws -> Int"
Los cierres pueden capturar valores de su alcance:
// This function returns another function which returns an integer
func makeProducer(x: Int) -> (() -> Int) {
let closure = { x } // x is captured by the closure
return closure
}
// These two function calls use the exact same code,
// but each closure has captured different values.
let three = makeProducer(3)
let four = makeProducer(4)
three() // returns 3
four() // returns 4
Los cierres se pueden pasar directamente a las funciones:
let squares = (1...10).map({ $0 * $0 }) // returns [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
let squares = (1...10).map { $0 * $0 }
NSURLSession.sharedSession().dataTaskWithURL(myURL,
completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in
if let data = data {
print("Request succeeded, data: \(data)")
} else {
print("Request failed: \(error)")
}
}).resume()
Variaciones de sintaxis
La sintaxis básica de cierre es
{
[
lista de captura]
(
parámetros)
throws-ness->
return typein
body}
.
Muchas de estas partes se pueden omitir, por lo que hay varias formas equivalentes de escribir cierres simples:
let addOne = { [] (x: Int) -> Int in return x + 1 }
let addOne = { [] (x: Int) -> Int in x + 1 }
let addOne = { (x: Int) -> Int in x + 1 }
let addOne = { x -> Int in x + 1 }
let addOne = { x in x + 1 }
let addOne = { $0 + 1 }
let addOneOrThrow = { [] (x: Int) throws -> Int in return x + 1 }
let addOneOrThrow = { [] (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { x throws -> Int in x + 1 }
let addOneOrThrow = { x throws in x + 1 }
- La lista de captura se puede omitir si está vacía.
- Los parámetros no necesitan anotaciones de tipo si se pueden inferir sus tipos.
- No es necesario especificar el tipo de retorno si se puede inferir.
- Los parámetros no tienen que ser nombrados; en su lugar, se pueden referir con
$0
,$1
,$2
, etc. - Si el cierre contiene una sola expresión, cuyo valor debe devolverse, se puede omitir la palabra clave
return
. - Si se infiere que el cierre produce un error, se escribe en un contexto que espera un cierre de lanzamiento, o si no se produce un error, se pueden omitir los
throws
.
// The closure's type is unknown, so we have to specify the type of x and y.
// The output type is inferred to be Int, because the + operator for Ints returns Int.
let addInts = { (x: Int, y: Int) in x + y }
// The closure's type is specified, so we can omit the parameters' type annotations.
let addInts: (Int, Int) -> Int = { x, y in x + y }
let addInts: (Int, Int) -> Int = { $0 + $1 }
Pasando cierres a funciones
Las funciones pueden aceptar cierres (u otras funciones) como parámetros:
func foo(value: Double, block: () -> Void) { ... }
func foo(value: Double, block: Int -> Int) { ... }
func foo(value: Double, block: (Int, Int) -> String) { ... }
Sintaxis de cierre de seguimiento
Si el último parámetro de una función es un cierre, las llaves de cierre {
/ }
se pueden escribir después de la invocación de la función:
foo(3.5, block: { print("Hello") })
foo(3.5) { print("Hello") }
dispatch_async(dispatch_get_main_queue(), {
print("Hello from the main queue")
})
dispatch_async(dispatch_get_main_queue()) {
print("Hello from the main queue")
}
Si el único argumento de una función es un cierre, también puede omitir el par de paréntesis ()
al llamar con la sintaxis de cierre final:
func bar(block: () -> Void) { ... }
bar() { print("Hello") }
bar { print("Hello") }
Parámetros de @noescape
Los parámetros de cierre marcados con @noescape
están garantizados para ejecutarse antes de que se devuelva la llamada a la función, por lo que se usa self.
No se requiere dentro del cuerpo de cierre:
func executeNow(@noescape block: () -> Void) {
// Since `block` is @noescape, it's illegal to store it to an external variable.
// We can only call it right here.
block()
}
func executeLater(block: () -> Void) {
dispatch_async(dispatch_get_main_queue()) {
// Some time in the future...
block()
}
}
class MyClass {
var x = 0
func showExamples() {
// error: reference to property 'x' in closure requires explicit 'self.' to make capture semantics explicit
executeLater { x = 1 }
executeLater { self.x = 2 } // ok, the closure explicitly captures self
// Here "self." is not required, because executeNow() takes a @noescape block.
executeNow { x = 3 }
// Again, self. is not required, because map() uses @noescape.
[1, 2, 3].map { $0 + x }
}
}
Nota de Swift 3:
Tenga en cuenta que en Swift 3, ya no marca bloques como @noescape. Los bloques ahora no se escapan por defecto. En Swift 3, en lugar de marcar un cierre como sin escape, se marca un parámetro de función que es un cierre con escape mediante la palabra clave "@escaping".
throws
y rethrows
Los cierres, como otras funciones, pueden arrojar errores :
func executeNowOrIgnoreError(block: () throws -> Void) {
do {
try block()
} catch {
print("error: \(error)")
}
}
La función puede, por supuesto, pasar el error a su interlocutor:
func executeNowOrThrow(block: () throws -> Void) throws {
try block()
}
Sin embargo, si el bloque que se pasa no se lanza, la persona que llama sigue con la función de lanzar:
// It's annoying that this requires "try", because "print()" can't throw!
try executeNowOrThrow { print("Just printing, no errors here!") }
La solución es rethrows
, lo que designa que la función solo se puede lanzar si su parámetro de cierre se lanza :
func executeNowOrRethrow(block: () throws -> Void) rethrows {
try block()
}
// "try" is not required here, because the block can't throw an error.
executeNowOrRethrow { print("No errors are thrown from this closure") }
// This block can throw an error, so "try" is required.
try executeNowOrRethrow { throw MyError.Example }
Muchas funciones de biblioteca estándar utilizan rethrows
, incluidos map()
, filter()
e indexOf()
.
Capturas, referencias fuertes / débiles y ciclos de retención.
class MyClass {
func sayHi() { print("Hello") }
deinit { print("Goodbye") }
}
Cuando un cierre captura un tipo de referencia (una instancia de clase), mantiene una referencia segura por defecto:
let closure: () -> Void
do {
let obj = MyClass()
// Captures a strong reference to `obj`: the object will be kept alive
// as long as the closure itself is alive.
closure = { obj.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope
closure() // The object is still alive; prints "Hello"
La lista de captura del cierre se puede usar para especificar una referencia débil o sin dueño:
let closure: () -> Void
do {
let obj = MyClass()
// Captures a weak reference to `obj`: the closure will not keep the object alive;
// the object becomes optional inside the closure.
closure = { [weak obj] in obj?.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure() // `obj` is nil from inside the closure; this does not print anything.
let closure: () -> Void
do {
let obj = MyClass()
// Captures an unowned reference to `obj`: the closure will not keep the object alive;
// the object is always assumed to be accessible while the closure is alive.
closure = { [unowned obj] in obj.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure() // crash! obj is being accessed after it's deallocated.
Para obtener más información, consulte el tema Administración de memoria y la sección Recuento automático de referencias de The Swift Programming Language.
Retener ciclos
Si un objeto mantiene un cierre, que también contiene una fuerte referencia al objeto, este es un ciclo de retención . A menos que el ciclo se rompa, la memoria que almacena el objeto y el cierre se filtrará (nunca se recuperará).
class Game {
var score = 0
let controller: GCController
init(controller: GCController) {
self.controller = controller
// BAD: the block captures self strongly, but self holds the controller
// (and thus the block) strongly, which is a cycle.
self.controller.controllerPausedHandler = {
let curScore = self.score
print("Pause button pressed; current score: \(curScore)")
}
// SOLUTION: use `weak self` to break the cycle.
self.controller.controllerPausedHandler = { [weak self] in
guard let strongSelf = self else { return }
let curScore = strongSelf.score
print("Pause button pressed; current score: \(curScore)")
}
}
}
Utilización de cierres para codificación asíncrona.
Los cierres a menudo se utilizan para tareas asíncronas, por ejemplo, cuando se obtienen datos de un sitio web.
func getData(urlString: String, callback: (result: NSData?) -> Void) {
// Turn the URL string into an NSURLRequest.
guard let url = NSURL(string: urlString) else { return }
let request = NSURLRequest(URL: url)
// Asynchronously fetch data from the given URL.
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {(data: NSData?, response: NSURLResponse?, error: NSError?) in
// We now have the NSData response from the website.
// We can get it "out" of the function by using the callback
// that was passed to this function as a parameter.
callback(result: data)
}
task.resume()
}
Esta función es asíncrona, por lo que no bloqueará el subproceso al que se está llamando (no se congelará la interfaz si se llama en el subproceso principal de su aplicación GUI).
print("1. Going to call getData")
getData("http://www.example.com") {(result: NSData?) -> Void in
// Called when the data from http://www.example.com has been fetched.
print("2. Fetched data")
}
print("3. Called getData")
Debido a que la tarea es asíncrona, la salida generalmente se verá así:
"1. Going to call getData"
"3. Called getData"
"2. Fetched data"
Debido a que el código dentro del cierre, print("2. Fetched data")
, no se llamará hasta que se print("2. Fetched data")
los datos de la URL.
Cierres y alias de tipo
Un cierre puede definirse con un typealias
. Esto proporciona un tipo de marcador de posición conveniente si se utiliza la misma firma de cierre en varios lugares. Por ejemplo, las devoluciones de llamadas de solicitud de red comunes o los controladores de eventos de interfaz de usuario son excelentes candidatos para ser "nombrados" con un alias de tipo.
public typealias ClosureType = (x: Int, y: Int) -> Int
A continuación, puede definir una función utilizando las tipografías:
public func closureFunction(closure: ClosureType) {
let z = closure(1, 2)
}
closureFunction() { (x: Int, y: Int) -> Int in return x + y }