I'm looking for the best practice for storing functions as variable in other objects. Specifically, I'm looking to avoid retain cycles inherent in capturing self
in the function.
Coming from objective-c and blocks, I would normally do something like this:
__weak id _self = self;
iVar.someBlock = ^{
[_self doSomething];
};
Of course, the iVar
class would copy the block and store it. No retain cycle exists because I've capture __weak id _self
.
In Swift, I'm a little less certain, especially since I can pass class functions/methods. So, let's say in the iVar
class I have:
class iVarClass {
var callBack:() -> ()?
func jumpUpAndDown(){
//Weeeeeee!
self.callBack?()
}
}
Now in my "main" class I have an instance variable of the above class and I do:
class mainClass {
var iVar: iVarClass
init(iVar:iVarClass){
self.iVar = iVar
iVar.callback = self.doSomething
}
func doSomething(){
self.iVar.jumpUpAndDown?()
}
}
Do we have a retain cycle here? I would think so, and I think that perhaps I need to make the callback
weak:
weak var callBack:() -> ()?
Of course, I could do something like this in the main class:
init(iVar:iVarClass){
self.iVar = iVar
weak var _self = self
iVar.callback = {
_self?.doSomething()
}
}
But it's so nice to be able to pass class functions as arguments! Also, if I do need to make the callback
weak, then I think I would loose the ability to assign it a closure (because after the assignment, the closure would be released from memory with only one weak reference).
Also, notice how the onus for memory management responsibility is on the receiver now instead of the assigner, but since the receiver cannot know the source of the assignment it can't really be held responsible. In other words, there must now be a implicit contract between the receiver and the assigner on what kind of function is to be passed, which is fragile and not-recommended. When the assigner is responsible, it can take steps to ensure there's no retain cycle, but the receiver cannot take such steps.
This makes me think that we should never pass a class function to another object. It's too dangerous. You can't know how the receiver will store/use it.
Or am I missing something? Does Swift magically resolve this problem behind the scenes?
Update
@Kirsteins pointed out something I'd forgotten about: capture lists. So instead of explicitly declaring weak var _self = self
, You can declare it in the closure instead:
init(iVar:iVarClass){
self.iVar = iVar
iVar.callback = { [weak self] in
self?.doSomething()
}
}
This is better, but not so nearly as elegant as simply assigning the class function.
I think what I want is for Swift to auto convert a class function into a closure with a capture list so I don't have to do it. To be fair, it's not exactly difficult, but certainly is a lot prettier if I could just assign the class function. Hell, even this would be nicer:
self.iVar.callback = weak self.doSomething