Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime of retained memory in Swift closures

In the Advanced Swift talk from WWDC 2014, the speaker gave this example of a function memoizer using generics:

func memoize<T: Hashable, U>( body: (T)->U ) -> (T)->U {
    var memo = Dictionary<T, U>()
    return { x in
        if let q = memo[x] { return q }
        let r = body(x)
        memo[x] = r
        return r
    }
}

I'm having trouble wrapping my head around the lifetime of that memo var. Does each invocation of the memoized fibonacci function hold a strong reference to it? And if so, how would you release that memory when you're done with it?

like image 823
Jamie Forrest Avatar asked Jun 20 '14 15:06

Jamie Forrest


2 Answers

In C/Objective-C Blocks terminology, memo is a __block variable (in Swift, you don't need to explicitly write __block to capture variables by reference). The variable can be assigned to in a block (closure) and all scopes that see that variable will see changes from any other variable (they share a reference to the variable). The variable will be valid as long as some block (closure) that uses it (there is just one block that uses it in this case) is still alive. After the last block that uses it is deallocated, the variable goes out of scope. How that works is an implementation detail.

If this variable had object pointer type, then the object pointed to would be retained by any blocks that capture it. However, in this case, the variable is Dictionary, a struct type, which is a value type. Thus there is no memory management to worry about. The variable is the struct, and the struct lives for as long as the variable. (The struct itself may allocate memory elsewhere, and free it in its destructor, but that's completely internally handled by the struct and the outside should not know or care about it.)

One generally does not need to worry about how __block variables work internally. But basically, the variable is wrapped in a simplified "object" kind of thing, with the actual "variable" being a field of this "object", which is memory-managed through reference counting. Blocks that capture them hold "strong references" to this pseudo-object thing -- when a block is created on the heap (technically, when they are copied from stack blocks to the heap) that uses this __block variable, it increases the reference count; when a block that uses it is deallocated, it decreases the reference count. When the reference count goes to 0, this pseudo-"object" is deallocated, invoking the appropriate destructor for its variable type first.

To answer your question, the "memorized fibonacci function" is a Block (closure). And it is that that holds a strong reference to whatever holds the memo variable. "Invocations" don't have strong or weak references to it; when a function is invoked, it uses the reference that the function itself has. The lifetime of the memo variable is the lifetime of the "memorized fibonacci function" in this case, because it is the only closure that captures this variable.

like image 97
newacct Avatar answered Oct 23 '22 19:10

newacct


Each invocation of memoize() will create its own memo variable, independent of other invocations. It lives as long as the closure referencing it lives; when the closure is released, the variables captured by it (in this case, memo) are released too.

You can think of this as of returning a struct like in this pseudocode:

struct Closure<T,U>
{
    var memo: Dictionary<T, U>
    func call(t: T): U
    {
        ....
    }
}

func memoize<T: Hashable, U>( body: (T)->U ) -> Closure<T,U>
{
    return Closure(memo: Dictionary<T, U>())
}
like image 3
hamstergene Avatar answered Oct 23 '22 19:10

hamstergene