func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal = amount
return runningTotal
}
return incrementer
}
So I'm reading swift documentation and they define some function here with a closure inside it that basically uses the variables that are defined locally in makeIncrementer now this makes sense to me but then this is said:
func incrementer() -> Int {
runningTotal = amount
return runningTotal
}
The incrementer() function doesn’t have any parameters, and yet it refers to runningTotal and amount from within its function body. It does this by capturing a reference to runningTotal and amount from the surrounding function and using them within its own function body. Capturing by reference ensures that runningTotal and amount don’t disappear when the call to makeIncrementer ends, and also ensures that runningTotal is available the next time the incrementer function is called.
Now to me this doesn't make sense? Capturing by reference ensures that runningTotal and amount don't disappear when the call to makeIncrementer ends? When that call ends don't those variables just get freed and the memory for them is basically just gone? They don't exist anymore? Until I call that function again in which the memory is allocated for them again but they are new variables am I wrong here?
And why would runningTotal not be available the next time the incrementer function is called? It's always created and initalized in makeIncrementer how would there ever be a case that incrementer cannot access runningTotal?
CodePudding user response:
Capturing by reference ensures that runningTotal and amount don't disappear when the call to makeIncrementer ends? When that call ends don't those variables just get freed and the memory for them is basically just gone?
The key thing to notice here is that makeIncrementer
returns a new function object, which is what has captured those variables. Normally, you would be correct: runningTotal
is a local variable, so at the end of makeIncrementer
, it would normally "go away".
Except, the incrementer
function still needs to access it; the compiler notices this, and effectively extends the lifetime of the variable outside of the scope of makeIncrementer
. Its lifetime is tied to the lifetime of the function object you return, and when that object goes away, so does the runningTotal
variable. If you store the result of calling makeIncrementer
in a property, for example, the runningTotal
that was created inside of makeIncrementer
would stick around until you assigned something else to that property; if you call makeIncrementer
and don't do anything with the result, though, then it would go away immediately, as it's not needed anymore.
Until I call that function again in which the memory is allocated for them again but they are new variables am I wrong here?
Every time you call makeIncrementer
, you create a new local runningTotal
variable that is distinct and separate from every other runningTotal
variable created by other calls to makeIncrementer
. Each of those variables is separately held on to by different functions created by every single call to makeIncrementer
totally separately.
So:
- Yes, every time you call
makeIncrementer
, memory is used for a newrunningTotal
variable, as it's a new variable, and - Because each new call creates a new variable which is captured by a newly-created function object, each individual
incrementer
will always have access to therunningTotal
it captures — the point of the capture is to ensure that the variable will be accesible so long as the function object could be called
And why would runningTotal not be available the next time the incrementer function is called? It's always created and initalized in makeIncrementer how would there ever be a case that incrementer cannot access runningTotal?
If the compiler didn't take care to extend the lifetime of the runningTotal
variable, then when makeIncrementer
returned an incrementer
function object, the runningTotal
memory would "go away" and potentially get reused for something else entirely (because the variable is local). If incrementer
would try to use that runningTotal
reference it thinks it has, it would be manipulating random memory, which would cause some serious problems.
Instead, Swift makes sure that this is safe, and that it can't go wrong.