Pendulum

Reference counting (ARC) in iOS is awesome: It is fast, predictable, and uses less RAM. Sometimes it can be a challenge to work with, though. Timers are one of those things.

It is really easy to accidentally create a circular reference with timers in iOS. A circular reference is where A references B, B references C, and C references A. None of those objects can be released, because their reference counts can never reach zero. This is a cause of app instability, and if you understand how to avoid this, you'll be a better developer.

Timers in particular can trip you up here, because there's a lot of references to be aware of:

  • The NSRunLoop references your Timer (if scheduled, which it should be).
  • Timer references its target/block.
  • Blocks can easily retain your View Controller/etc.

One more thing to beware:

  • The Timer won't stop if eg your view controller sets it to nil, because the NSRunLoop is referencing it, keeping it in memory.

So here's Pendulum and Countdown, my solution to all these issues:

/// This is a wrapper around a repeating Timer.
/// You don't need to worry about invalidating it, just set it
/// to nil and that's taken care of for you.
/// This can be handy for if you want to use this eg in a state
/// machine enum: Simply change the state and the timer vanishes.
/// Be careful that your closure doesn't strongly reference self,
/// or you may cause a retain loop, such as:
/// MyViewController -> Pendulum -> closure -> MyViewController.
class Pendulum {
    let timer: Timer

    init(seconds: TimeInterval, closure: @escaping () -> ()) {
        timer = Timer.scheduledTimer(withTimeInterval: seconds,
                repeats: true, block: { _ in
            closure();
        })
    }

    deinit {
        timer.invalidate()
    }
}

/// This is like Pendulum, but does not repeat.
class Countdown {
    let timer: Timer

    init(seconds: TimeInterval, closure: @escaping () -> ()) {
        timer = Timer.scheduledTimer(withTimeInterval: seconds,
                repeats: false, block: { _ in
            closure();
        })
    }

    deinit {
        timer.invalidate()
    }
}

And here's how to use it:

class MyViewController: UIViewController {

    var pendulum: Pendulum?

    override func viewDidLoad() {
        super.viewDidLoad()

        pendulum = Pendulum(seconds: 1, closure: {
            [weak self] in
            self?.doSomethingEverySecond()
        })
    }

    func doSomethingEverySecond() {
        // ...
    }

}

Thanks for reading, and have a great week!

Photo by Jon Tyson on Unsplash

Thanks for reading! And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.

Chris Hulbert

(Comp Sci, Hons - UTS)

iOS Developer (Freelancer / Contractor) in Australia.

I have worked at places such as Google, Cochlear, Assembly Payments, News Corp, Fox Sports, NineMSN, FetchTV, Woolworths, and Westpac, among others. If you're looking for help developing an iOS app, drop me a line!

Get in touch:
[email protected]
github.com/chrishulbert
linkedin
my resume



 Subscribe via RSS