Swifty APIs: NSTimer
May 11, 2015
Hey! This is an old post. It reflects my views and the state of the world in 2015, but is likely to be outdated. You’ll find me
on Twitter, building
Nozbe, and making
engineering blueprints.
In the previous installment of Swifty APIs, we took a look at NSUserDefaults
and gave it a make-over. I pointed out some of its problems, like verbosity and inconsistent behavior, and tried to fix them. The resulting interface was, arguably, cleaner, easier to use, and felt more at home with Swift’s syntax.
Today on my plate is NSTimer
. This class has been a pet peeve of mine for a while. Perhaps I’ve been using it more often than most people (fun fact: one of my apps is essentially a fancy, special-purpose timer), but I really do think that for the simple, almost trivial function it serves, its API seems unnecessarily inconvenient. It feels outdated not just by Swift standards — it stayed unchanged for at least 15 years, despite advancements in Objective-C.
Either way, let’s make it better.
The name
Here’s our victim:
Our first order of business will be to change the name of this method to something slightly less verbose.
Why do we have to say “timer” in scheduledTimerWithTimeInterval
? We just said “NSTimer”, what else could we possibly be scheduling? Why say “time interval”, when “interval” would do? Again, it’s a timer, of course the interval is about time. And the word “with” is pure syntactic noise. It explains nothing to the reader. How about this:
It’s a subtle difference, of course, but by making things shorter, we’re reducing the volume of code we have to read and understand. Brevity for its own sake is not the point. The point is to remove noise — words and constructs that take up space, but convey very little information. We’re trying to distill our code to only things that actually matter.
(This is not an easy topic that I can explain in one paragraph. I encourage you to read Swifty methods where I dig deep into the nuances of naming things in search of clarity.)
Closing over our target
Let’s see how we’re doing:
Ouch. Look at all that code. We might have shortened the name, but there’s still a lot of work to do.
The most frustrating thing here is that we have to define a new method just to schedule a timer. That seems unreasonable to me. Yes, sometimes you’d want to do that anyway, but often times I just want to quickly debug something or run a few lines of code with a timer.
You know where this is going. Let’s get rid of the target/selector pair and replace it with a closure.
So easy, and so much better.
Passing data
There’s a very good chance you know what userInfo
is. But in case you don’t, it’s a simple mechanism of passing data between methods. It works like this:
We don’t need that anymore, though. The whole beauty of closures is that, in addition to defining a function inline, they can access variables in the defining scope.
Closures are such a superior solution to target
, selector
& userInfo
. If we want to run just a few lines of code, we can do that inline. If we want to call existing method with arguments, we can define a one-line closure and pass parameters from the defining scope. And if we want to call a method without any arguments, we can simply pass a reference to that method instead of a closure — Swift treats them as equivalent.
Delaying and repeating
With the use of closures, we’ve gotten to a pretty good shape. We designed a perfectly respectable API, and we could very well leave it at that.
But let’s try something different. Instead of passing the repeats
parameter, how about we make two separate methods, for repeating and non-repeating timers:
I like this. It makes a lot of sense for me to make those two different methods. Under the hood, timer repetition might be just a parameter. But from the API user point of view, I see it as two different jobs to be done. Sometimes I want to delay something, and sometimes I want to repeat something. I’ve never encountered a situation where I would actually want to pass a variable to repeats
, and not just a true
/false
constant.
And hey, we also made these invocations a bit shorter. Again, not our main goal, but it certainly doesn’t hurt.
Clearer time intervals
The purpose of Swifty APIs isn’t just to make something cool and take advantage of Swift’s features. There’s some of it (because why not) but the ultimate goal is to produce an API that’s easier to use and understand. We want the interface to be readable and clear.
And as far as clarity is concerned, one thing that bothers me is how we express time intervals. I mean, what’s 5.0
? Five of what? You can usually infer from context that it is a time interval, and you can probably guess that it’s measured in seconds… But I think we can do better. We can make this clearer, without being too verbose.
Here’s an idea I stole from Ruby on Rails:
Wow. Isn’t this… cute? But it’s so clear, readable, and expressive, too!
It sounds almost like English: “every 1 minute”, “after 5 seconds”. Usually when people try to make a programming language read like English, they make it worse, and you end up with something that’s harder, not easier to understand. But not here. We just got ourselves a natural-sounding and expressive API that everyone will immediately understand, yet it’s almost as terse as it can possibly be.
Implementation
Now that we’ve designed an alternative interface for NSTimer
, it’s time to write the code.
Let’s start with the hardest part — closures. Remember, NSTimer
requires a target object and a method on that object to run. Swift functions are not objects, so we’ll need to introduce a helper class to wrap the closure.
This is a bridge between the closure-based API and the target/selector reality. We can initialize this object with a closure, and then call fire
to run it. (The fire
method needs to be @objc
to work with NSTimer
.)
Now we can do:
In case you’re wondering about memory management, the rules are on our side. NSTimer
keeps a strong reference to its target object, so as long as the timer is alive, the “actor” object will be alive, and so will our closure.
Great! Now let’s just package this together. But before we define every
and after
, let’s define timer initializers. Why? Sometimes you don’t want your timer to be immediately scheduled. And there are some situations where you want to add a timer to the run loop yourself.
Wait, wait, wait… class func new
? That’s not an initializer! No, it’s not, but unfortunately, as of Swift 1.2, there’s a bug (18720947) that will crash this code if we define it as a convenience initializer. Calling this new
seems like a reasonable compromise.
Now we can define every
and after
in terms of new
:
And finally, the icing on the cake, let’s implement our NSTimeInterval
helpers:
There’s a fair amount of repetition here. But the goal of these helpers is to be expressive and natural. So we have singular and plural variants — this way we can naturally write 1.second
, 5.minutes
, 0.7.seconds
. (Because Double
is IntegerLiteralConvertible
, we can use these properties on integers for free, without extending Int
.)
Result
Here’s our new, freshly baked NSTimer
API:
And that’s it! Another class simplified, modernized, swiftified.
If you like it, I published the full source code on GitHub: SwiftyTimer. If you’re using CocoaPods, you can include this library in your project by adding pod 'SwiftyTimer'
to your Podfile.
I also encourage you to check out my previous Swifty API project with NSUserDefaults, and my article about naming things: Swifty methods.
If you have comments, complaints or ideas for improvement, please let me know on Twitter or make an issue on GitHub.
Published May 11, 2015.
Last updated May 15, 2015.
Send feedback.