David Cordero

iOS and tvOS developer at @Zattoo. Amateur Triathlete. Passionate about coding and lifelong learning.

NSTimer stands for NoSwiftyTimer

22 May 2017 » swift, ios

NSTimer is one of the simplest mechanism provided by Apple to create timers.

You probably saw it applied to several purposes: Views being shown or hidden after a certain delay, periodically updated views, or any other task being executed periodically or with a delay. They are without any question a very generic and powerful tool.

A NSTimer is basically just waiting until a certain time interval has elapsed and then firing a specific message to its target.

And as you can see, I am speaking here about messages and targets. Here is where the first issue arises. Targets and messages do not sound very Swifty right?.

Recently I had to work NSTimers and I found a couple of annoying details on them.

First of all, as we saw it has a very old fashion syntax based on Objective-C runtime, working with targets and selectors instead of closures which make them look like a fish out of water in my Swift code.

On the other hand, and more important, NSTimers are one of the easiest ways to introduce a memory leak in your App if you don’t pay attention to ARC. Why? Well, check the following example:

import Foundation

final class MyClass {

    private var myTimer: NSTimer?

    init() {
        print("Hello")
    }

    func setUpTimer() {
        myTimer = .scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(timerHandler), userInfo: nil, repeats: true)
    }
    
    @objc func timerHandler() {
        print("Timer handler")
    }
    
    deinit {
        print("Bye Bye")
        myTimer?.invalidate()
    }
}

var myInstance: MyClass?
myInstance = MyClass()
myInstance?.setUpTimer()
myInstance = nil

Pretty straightforward, isn’t it? But if you copy this code to a playground you will see that it generates the following output:

Hello

Hey, you must be wondering now where is the “Bye Bye” trace and why it wasn’t printed in the console right?

Well, the reason is because a NSTimer maintains a strong reference to its target. So even thought the previous example invalidates the timer on its deinit, this invalidation is actually never executed because myInstance is being retained by its timer.

image

From NSTimer to SwiftyTimer

Apple leaves a very clear message on the documentation of NSTimer:

Subclassing Notes: You should not attempt to subclass NSTimer.

Due to this reason, I decided to use composition to create my custom Swifty version of NSTimers. Basically wrapping them to sweep its annoying parts under the carpet.

But it would allow me to solve both problems, to get a more swifty syntax and to make it easier to avoid memory leaks.

import Foundation

final public class SwiftyTimer {
    
    private var timer: NSTimer?
    
    private let callback: () -> Void
    
    public init(scheduledTimerWithTimeInterval timeInterval: NSTimeInterval, userInfo: AnyObject? = nil, repeats: Bool, callback: () -> Void) {
        self.callback = callback
        self.timer = .scheduledTimerWithTimeInterval(timeInterval, target: self, selector: #selector(invokeCallback), userInfo: userInfo, repeats: repeats)
    }
    
    public func invalidate() {
        timer?.invalidate()
    }
    
    // MARK: — Private
    
    @objc private func invokeCallback() {
        callback()
    }
}

With this simple wrapper, we were able to avoid the strong reference from the timer in a very simple and readable way.

final class MyClass {
    
    private var myTimer: SwiftyTimer?
    
    init() {
        print("Hello")
    }
    
    func setUpTimer() {
        myTimer = SwiftyTimer(scheduledTimerWithTimeInterval: 1.0, repeats: true) {
            print("Timer handler")
        }
    }
    
    deinit {
        print("Bye Bye")
        myTimer?.invalidate()
    }
}

Once again, if you copy this code to a playground you will see that it now generates a different output:

Hello Bye Bye

Thanks to the additional layer, we were able to avoid the memory leak because what NSTimer is now retaining is not the code using the timer but an instance of SwiftyTimer instead.

Of course, we still have a retain cycle among SwiftyTimer and NSTimer, but this is something we can easily break making use of the method invalidate of SwiftyTimer.

At this point it was also a very easy task to Swiftify the usage of timers, using default values for the parameters and taking advantage of closures.

Getting at the end a more readable and less error prone solution.

.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(timerHandler), userInfo: nil, repeats: true)

// vs
SwiftyTimer(scheduledTimerWithTimeInterval: 1.0, repeats: true) { }

Conclusion

Probably (and hopefully), Apple will give soon all the love and tender to those APIs that still need it, but in the meanwhile, they are a good excuse to be creative, getting as result more readable and safety code… Don’t be shy !!!

Feel free to add me on github, twitter or dcordero.me if you have any question.