Search code examples
swiftmemory-leakscallbackclosuresweak-references

Preventing memory leaks when referencing a callback in Swift


NOTE: This question has to do with Swift, closures, callbacks, and memory leaks... the rest is for illustration purposes.

Consider we have a class, ValueAnimator, that has the following initializer:

init(durationInSeconds: Int, sampleRate: Int, interpolation: Interpolator,
         callback: @escaping (Double) -> Void) {
        self.maxIterations = durationInSeconds * sampleRate
        self.timeInterval = 1.0 / Double(sampleRate)
        self.interpolation = interpolation
        self.callback = callback
    }

As you can see we have a callback that is passed in.

Now, consider these two different ways of defining the callback when initializing the ValueAnimator from a ViewController:

Option one, define the callback in-line:

class ViewController: ViewController {

    var valueAnimator: ValueAnimator?

    override func viewDidLoad() {
        valueAnimator = ValueAnimator(durationInSeconds: 2, sampleRate: 2,
                                      interpolation: .sineWaveFrom0To1To0)
        { [weak self] value in
            guard let self = self else { return }

            // Do something with value ...
        }
        valueAnimator?.start()
    }

}

Option two, define the callback as a separate function and referencing it:

(I would prefer this way of doing it... because I find it is less of a "cognitive load" not to have closures within closures within closures... hence then question)

class ViewController: ViewController {

    var valueAnimator: ValueAnimator?

    override func viewDidLoad() {
        valueAnimator = ValueAnimator(durationInSeconds: 2, sampleRate: 2,
                                       interpolation: .sineWaveFrom0To1To0, callback: theCallback)
        valueAnimator?.start()
    }

    func theCallback(value: Double) {

        // Do something with the value ...

    }

}

As you can see with the first version, there is some protection against a reference cycle.

Is there some way of applying this same protection in the second version... or is it not necessary for some reason?

Thankyou!


Solution

  • To apply that same proptection to the second way send self instead of the callback and in your class ValueAnimator define

    weak var delegate:ViewController?
    

    Then inside the response do

    delegate?.callback(value:<#value#>)