Reputation: 1851
From Apple Swift programming guide (2.2) it states that
A closure can capture constants and variables from the surrounding context in which it is defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.
Example:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
The question arises how does the value get saved and not reset? I come from a C background and this looks to me like 3 function calls and 3 different stack frames. But it seems the value isn't overridden. Does it mean that if a closure uses its surrounding context variable it does not get erased by the compiler and can cause memory leaks if not used correctly?
Upvotes: 0
Views: 674
Reputation: 534885
Does it mean that if a closure uses its surrounding context variable it does not get erased by the compiler and can cause memory leaks if not used correctly?
Absolutely, but that's not what is happening in this case. Let me break it down. Start inside the makeIncrementer
function:
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
The func incrementer
refers to runningTotal
which is in scope from outside its own body. Therefore runningTotal
is captured by func incrementer
.
Now consider what the surrounding function makeIncrementer
does:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
// ...
func incrementer() -> Int {
// ...
}
return incrementer
}
It declares func incrementer
and returns it. So now consider what happens when we call that surrounding function:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
// ...
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
That last line calls makeIncrementer
, which we just said returns a function. It also assigns that function to a variable, incrementByTen
. That is a local (automatic) variable. So yes, this function is being retained, but only so long as incrementByTen
lives, which will not be long because it's just on the stack as an automatic local variable.
But as long as incrementByTen
does live, it holds that function, and that function, we said at the start, has captured runningTotal
and is maintaining it in a kind of floating local space. Therefore the function pointed to by incrementByTen
can be called multiple times and that floating runningTotal
keeps living and keeps being incremented.
Upvotes: 2