lychee
lychee

Reputation: 1851

Capturing Values in Closure

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

Answers (1)

matt
matt

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

Related Questions