Passing a function vs a closure "weak self" as parameter in swift

Viewed 2133

In a lot of sample, people use closures to pass a function as a parameter :

sum.getSumOf(array: [1,2,3]) { [weak self] sum in
     self?.result = sum
     print(self?.result as Any)
}

I understand that is done for cycling issues. But what about passing a function rather than a closure ? How make this function "weak" ?

For example:

class Calculator {

    let sum = Sum()
    var result = 0

    // passing a function 

    func doSum() {
        sum.getSumOf(array: [1,2,3], handler: store(sum:))
    }

    // vs use closure

    func doSumWeakSelf() {
        sum.getSumOf(array: [1,2,3]) { [weak self] sum in
            self?.result = sum
            print(self?.result)
        }
    }

    func store(sum:Int){
        result = sum
        print(result)
    }
}

class Sum {

    func getSumOf(array:[Int], handler: ((Int)->Void)) {
        //step 2
        var sum: Int = 0
        for value in array {
            sum += value
        }

        handler(sum)
    }

}
3 Answers

First, in your example there is no need for weak at all. There is no retain loop, since the handler is not escaping. It is executed immediately and released. You shouldn't use weak in cases where there can not be a retain loop.

Even in escaping cases, there may not always be a retain loop, since the closure may eventually be released. When there is an @escaping annotation, however, you can only really know this through documentation.

That said, to your specific question, in this example:

func doSum() {
    sum.getSumOf(array: [1,2,3], handler: store(sum:))
}

This captures self (since it's an implicit parameter to store(sum:)), and it is not possible to make self weak using this syntax. That's ok in this case, since there is no retain loop, but if there were one, you could not resolve it if you choose to pass a method this way.

AFAIK, there is no built-in solution in Swift that would allow to pass a weak/unowned reference to a function (and I think I've seen this being discussed on Swift proposal forums). For now, you will need to wrap the function inside a closure:

sum.getSumOf(array: [1,2,3]) { [weak self] int in
    self?.store(sum: int)
}

There is no retain cycle or even delayed deinit in your "getSumOf" logic. Once "getSumOf" will be run then your "handler" will be executed without quitting the current thread and the method returns back to "doSum" method.

Example of delayed deinit of Calculator object:

In that case calculator will be released with 10 seconds delay

    var calculator: Calculator?

    calculator = Calculator()
    calculator.doSum()
    calculator = nil
    
    class Sum {
    
        func getSumOf(array:[Int], handler: @escaping ((Int)->Void)) {
            var sum: Int = 0
            for value in array {
                sum += value
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
                handler(sum)
            }
        }
    
    }

Example of retain cycle between Calculator and Sum objects:

In that case inside init of Calculator new Sum object receives a strong reference to calculator object which has also strong reference to sub object. To avoid this happening, the calculator variable inside Sum should have weak keyword.

        var calculator: Calculator?
    
        calculator = Calculator()
        calculator.doSum()
        calculator = nil

class Calculator {

    var sum: Sum
    
    init() {
        self.sum = Sum()
        self.sum.calculator = self
    }
    ...
}

class Sum {
    
    var calculator: Calculator?

    ...
}

"Retain cycles" more often happens between objects and "delayed deinit" is more often related to closures / functions

Related