-
-
Save ilyapuchka/8d0690101f23c6d09225 to your computer and use it in GitHub Desktop.
class A { | |
let factory: ()->() | |
var closure: ((b: B)->())? | |
init(factory: ()->()) { | |
self.factory = factory | |
} | |
deinit { | |
print("deinit A") | |
} | |
} | |
class B { | |
var array = [A]() | |
func addA(factory: ()->()) -> A { | |
let a = A(factory: factory) | |
self.array.append(a) | |
return a | |
} | |
func call() { | |
for a in array { | |
a.factory() | |
a.closure?(b: self) | |
} | |
} | |
deinit { | |
print("deinit B") | |
} | |
} | |
var b: B! = B() | |
b.addA({ | |
print("factory \(b)") | |
}).closure = { b in | |
print("closure \(b)") | |
} | |
b.addA({ | |
print("factory \(b)") | |
}).closure = { b in | |
print("closure \(b)") | |
} | |
b.call() | |
print("should deinit") | |
b = nil | |
b | |
/* | |
Output: | |
factory B | |
closure B | |
factory B | |
closure B | |
should deinit | |
deinit B | |
deinit A | |
deinit A | |
*/ |
Indeed, capturing self and variable pointing the instance (but not self!) is not the same.
This will cause retain cycle:
func addA() -> A {
let a = A(factory: {
print("factory \(self)")
})
self.array.append(a)
return a
}
And this obviously too:
func addA() -> A {
var b = self
let a = A(factory: {
print("factory \(b)")
})
b.array.append(a)
return a
}
Note that if you used something like
do {
let b = B()
// ...
print("should deinit")
// b goes out of scope here
}
// did not deinit!
it actually would be a retain cycle. Which is to say, letting a variable go out of scope is different than explicitly assigning nil
to it, when the variable has been captured.
Got here from the playground of Dip
Sorry to resurrect this old topic, but @kballard is absolutely right. I have just confirmed that it all breaks (leaks) if the declaration is changed from an optional to a simple variable (or if the b = nil
statement is removed)
So, there are two components to this behaviour:
- capturing an optional rather than a direct reference (doesn't depend on whether it's
self
or any other variable pointing to the same object; it only is a question of indirection which is present if it's an optional) - manually breaking the retain cycle by setting the optional pointee to nil. This is effective for all cases of capturing this particular optional because it's captured by reference and not by value.
Which brings us back to the Dip
s statement that it's safe to capture the container inside a factory - this statement doesn't seem to be valid, and this gist is actually proving that the retain cycle does exist, and has to be addressed somehow by the code.
For future reference here's the answer: https://twitter.com/jckarter/status/671812668005584896