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?
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.
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>())
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With