Swift Memory Management: Storing func in var

2020-06-18 07:21发布

问题:

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

回答1:

Couldn't you do something like this:

class mainClass {
    var iVar: iVarClass
    init(iVar:iVarClass){
        self.iVar = iVar
        func go() {
            self.doSomething()
        }
        iVar.callback = go
    }
    func doSomething(){
      self.iVar.jumpUpAndDown?()
    }
}

It is my understanding that this way you wouldn't be capturing self directly and thus would avoid a retain cycle.