ref – https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
http://alisoftware.github.io/swift/closures/2016/07/25/closure-capture-1/
functions and closures are reference types
Closures are reference types. This means that when you assign a closure to more than one variable they will refer to the same closure. This is different from value type which make a copy when you assign them to another variable or constant.
Whenever you assign a function or a closure to a constant or a variable, you are actually setting that constant or variable to be a reference to the function or closure.
1 2 3 4 5 6 7 8 9 10 11 |
// a closure that take one Int and return an Int var double: (Int) -> (Int) = { x in return 2 * x } double(2) // 4 // you can pass closures in your code, for example to other variables var alsoDouble = double alsoDouble(3) // 6 |
say incrementByTen is a function, we assign constant alsoIncrementByTen to it like so:
1 2 |
let alsoIncrementByTen = incrementByTen alsoIncrementByTen() |
Closures can capture and store references to any constants or variables from the context in which they are defined.
In Swift, closures capture the variables they reference: variables declared outside of the closure but that you use inside the closure are retained by the closure by default, to ensure they are still alive when the closure is executed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// a closure that has no parameters and return a String var hello: () -> (String) = { return "Hello!" } hello() // Hello! // a closure that take one Int and return an Int var double: (Int) -> (Int) = { x in return 2 * x } double(2) // 4 // you can pass closures in your code, for example to other variables var alsoDouble = double alsoDouble(3) // 6 |
Declaring a closure
The general syntax for declaring closures is:
1 2 3 |
{ (parameters) -> return type in statements } |
If the closure does not return any value you can omit the arrow (->) and the return type. This also applies to the case where the type of the closure can be infered.
1 2 3 |
{ (parameters) in statements } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var noParameterAndNoReturnValue: () -> () = { print("Hello!") } var noParameterAndReturnValue: () -> (Int) = { return 1000 } var oneParameterAndReturnValue: (Int) -> (Int) = { x in return x % 10 } var multipleParametersAndReturnValue: (String, String) -> (String) = { (first, second) -> String in return first + " " + second } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var noParameterAndNoReturnValue = { print("Hello!") } var noParameterAndReturnValue = { () -> Int in return 1000 } var oneParameterAndReturnValue = { (x: Int) -> Int in return x % 10 } var multipleParametersAndReturnValue = { (first: String, second: String) -> String in return first + " " + second } |
Capturing Values
In Swift, closures capture the variables they reference: variables declared outside of the closure but that you use inside the closure are retained by the closure by default, to ensure they are still alive when the closure is executed.
In the below example, say we declare an object Pokemon. We delay 1 second, so that this function finishes running and exits. Then we see that closure of delay function will fun “closure()”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func delay(seconds: NSTimeInterval, closure: ()->()) { let time = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))) dispatch_after(time, dispatch_get_main_queue()) { print("?") closure() } } func demo1() { let pokemon = Pokemon(name: "Mewtwo") print("before closure: \(pokemon)") delay(1) { print("inside closure: \(pokemon)") } print("bye") } |
That’s because the closure strongly captures the variable pokemon: as the Swift compiler sees that the closure references that pokemon variable inside the closure, it automatically captures it (strongly by default), so that this pokemon is alive as long as the closure itself is alive.
In this example, the closure itself gets released once it has been executed by GCD, so that’s when the pokemon’s deinit method gets called too.
IF Swift didn’t capture that pokemon variable automatically, that would mean that the pokemon variable would have had time to go out of scope when we reach the end of the demo1 function, and that pokemon would no longer exist when the closure would execute one second later… leading to a probable crash.
Captured variables evaluated on execution
If we have a closure that captures a pokemon variable, and that variable points to a first Pokemon instance, then a second Pokemon instance in the closure’s context, after the function exits, which pokemon instance will the closure reference?
1 2 3 4 5 6 7 8 9 |
func demo2() { var pokemon = Pokemon(name: "Pikachu") print("before closure: \(pokemon)") delay(1) { print("inside closure: \(pokemon)") } pokemon = Pokemon(name: "Mewtwo") print("after closure: \(pokemon)") } |
result:
1 2 3 4 5 6 7 8 |
before closure: <Pokemon Pikachu> <Pokemon Pikachu> escaped! after closure: <Pokemon Mewtwo> ..time delay here... inside closure: <Pokemon Mewtwo> <Pokemon Mewtwo> escaped! |
We print the new pokemon, not the old one! That’s because Swift captures variables by reference by default.
So here, we initialize pokemon to Pikachu, then we change its value to Mewtwo, so that Pikachu gets released — as no more variable retains it. Then one second later the closure gets executed and it prints the content of the variable pokemon that the closure captured by reference.
The closure didn’t capture “Pikachu” (the pokemon we got at the time the closure was created), but more a reference to the pokemon variable — that now evaluates to “Mewtwo” at the time the closure gets executed.
- Pickachu was created
- then the closure only captured a reference to the pokemon variable, not the actual Pickachu pokemon/value the variable contained.
- So when pokemon was assigned a new value “Mewtwo” later, Pikachu was not strongly referenced by anyone anymore and got released right away.
- But the pokemon variable (holding the “Mewtwo” pokemon at that time) was still strongly referenced by the closure
- So that’s the pokemon that was printed when the closure was executed one second later
- And that Mewtwo pokemon was only released once the closure was executed then released by GCD.
Capturing a variable to use in a closure as a constant copy
If you want to capture the value of a variable at the point of the closure creation, instead of having it evaluate only when the closure executes, you can use a capture list.
Capture lists are written between square brackets right after the closure’s opening bracket (and before the closure’s arguments / return type if any)3.
To capture the value of a variable at the point of the closure’s creation (instead of a reference to the variable itself), you can use the [localVar = varToCapture] capture list. Here’s what it looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func demo5() { var value = 42 print("before closure: \(value)") delay(1) { [constValue = value] in // captured 42, now any changes outside will not take effect print("inside closure: \(constValue)") } value = 1337 print("after closure: \(value)") } This will print: before closure: 42 after closure: 1337 ...time delay so demo5() exits... inside closure: 42 |
You can modify captured values in closures
Note that if the captured value is a var (and not a let), you can also modify the value from within the closure2.
1 2 3 4 5 6 7 8 9 10 11 12 |
func demo4() { var value = 42 print("before closure: \(value)") delay(1) { print("inside closure 1, before change: \(value)") value = 1337 print("inside closure 1, after change: \(value)") } delay(2) { print("inside closure 2: \(value)") } } |
This code prints the following:
1 2 3 4 5 6 |
before closure: 42 ? inside closure 1, before change: 42 inside closure 1, after change: 1337 ? inside closure 2: 1337 |
Essentially, this is what’s happening:
1 2 3 4 5 6 7 8 9 10 11 12 |
func demo6_equivalent() { var pokemon = Pokemon(name: "Pikachu") print("before closure: \(pokemon)") // here we create an intermediate variable to hold the instance // pointed by the variable at that point in the code: let pokemonCopy = pokemon delay(1) { print("inside closure: \(pokemonCopy)") } pokemon = Pokemon(name: "Mewtwo") print("after closure: \(pokemon)") } |
Pikachu is referenced a constant pokemonCopy.
1 2 3 4 5 6 7 8 |
before closure: <Pokemon Pikachu> after closure: <Pokemon Mewtwo> <Pokemon Mewtwo> escaped! ...time delay so demo6_equivalent() finishes running... inside closure: <Pokemon Pikachu> <Pokemon Pikachu> escaped! |
Here’s what happens:
- Pikachu is created
- then it is captured as a copy (capturing the value of the pokemon variable here) by the closure.
- So when a few lines below we assign pokemon to a new Pokemon “Mewtwo”, then “Pikachu” is not released just yet, as it’s still retained by the closure.
- When we exit the demo6 function’s scope, Mewtwo is released, as the pokemon variable itself — which was the only one strongly referencing it — is going out of scope.
- Then later, when the closure executes, it prints “Pikachu” because that was the Pokemon being captured at the closure creation’s time by the capture list.
- Then the closure is released by GCD, and so is the Pikachu pokemon which it was retaining.
Capture global context
In the beginning of the chapter I mentioned that closures can capture values. Let’s see what that means:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var number = 0 var addOne = { // closure captured outside var 'number' number += 1 } var printNumber = { print(number) } printNumber() // 0 addOne() // number is 1 printNumber() // 1 addOne() // number is 2 addOne() // number is 3 addOne() // number is 4 printNumber() // 4 |
So a closure can remember the reference of a variable or constant from its context and use it when it’s called. In the example above the number variable is in the global context so it would have been destroyed only when the program would stop executing.
closure captures a variable that is not in the global context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func makeIterator(from start: Int, step: Int) -> () -> Int { var i = start return { let currentValue = i //captures outer context i i += step //captures parameter step return currentValue } } var iterator = makeIterator(from: 1, step: 1) iterator() // 1 iterator() // 2 iterator() // 3 var anotherIterator = makeIterator(from: 1, step: 3) anotherIterator() // 1 anotherIterator() // 4 anotherIterator() // 7 anotherIterator() // 10 |