2

I have a view controller where I am trying to call Timer.scheduledTimer(withTimeInterval:repeats:block) by passing a function as block parameter, instead of creating a block on the fly. I have this view controller:

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        Timer.scheduledTimer(withTimeInterval: 5,
                             repeats: true,
                             block: onTimer)    
    }

    deinit {
        print("deinit \(self)")
    }

    func onTimer(_ timer: Timer) {
        print("Timer did fire")
    }
}

The call is retaining the view controller, so the controller is never deallocated.

I know I can make it work as I want by replacing the call with:

    Timer.scheduledTimer(withTimeInterval: 5,
                         repeats: true) { [weak self] timer in

                            self?.onTimer(timer)
    }

But I'd like to know if there is a way to send the onTimer method directly and avoid the retain cycle.

Thanks.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
manueGE
  • 1,169
  • 11
  • 14
  • Your tags already indicate you are using Swift. There is no need to have it as part of the title. – Léo Natan Oct 21 '16 at 17:51
  • 1
    You said it yourself, if you don't want a strong reference cycle, use a weak reference. Your timer references your instance method, and the instance references the timer. The only way to get around that is to either move the instance method, or the timer, so they don't reside in the same instance. But to what effect? – Alexander Oct 21 '16 at 17:53
  • If your `onTimer` doesn't need instance data, you can make it static, which will break the cycle – Alexander Oct 21 '16 at 17:58
  • Somewhat related: [How to remove strong reference cycle from closure from method?](http://stackoverflow.com/q/39899051/2976878) – Hamish Oct 21 '16 at 18:00
  • 1
    The timer registers itself on a system NSRunLoop, which is what fires the timer. Until you invalidate, the run loop will reference your closure, even after you've released all references in your user code – Alexander Oct 21 '16 at 18:01
  • Are you needing to do something when the timer fires? If not, there's no reason to have it in your VC in the first place. Either way, it's generally a good idea to separate timers from your UI (View Controller in this case) so you might consider separating it either way. – GetSwifty Oct 21 '16 at 18:07

1 Answers1

2

You should call the invalidate() method:

This method is the only way to remove a timer from an RunLoop object. The RunLoop object removes its strong reference to the timer, either just before the invalidate() method returns or at some later point.

If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

Somewhere in your code, you should implement:

timer.invalidate()

Hope this helped.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143