Delay
18th April, 2015 —
Now, wait just a minute…
When you’re creating a user interface, the ability to easily control when things happen is important. One issue you’ll run into quite quickly (I remember this from my Flash days) is that there will be times when you need to do something on the next stack frame (the next pass of the event/run loop). Node.js, for example, has process.nextTick()
, or you can use setTimeout(timeoutHandler, 0)
. If you use the latter — less efficient — technique, you can delay execution not just to the next stack frame but by any number of seconds.
Having a simple, expressive way to delay execution is something that will make your development life easier and it’s great when troubleshooting UI issues.
In Cocoa, we can use either NSTimer or Grand Central Dispatch for this. Neither method is optimised for authoring, however, so I put together a very simple library called Delay based on the work of Evgenii Rtishchev and Chris Brind (with thanks to Cezary Wojcik for the link to Chris’s work.)
Code
Grab the code from our Git repository: source.small-tech.org/project/delay
Usage
Execute on next stack frame
Swift: delay(0.0)
{
// Do something.
}
(Note: you can also use dispatch_async
for this and, like nextTick()
in Node, it’s meant to be more efficient.
Execute after an arbitrary number of seconds
Swift: delay(42.0)
{
// Do something.
}
Cancel before execution
Swift: let cancellableCommand = delay(42.0)
{
// Do something.
}
cancellableCommand.cancel()
Throttling
Being able to cancel execution is especially important when you want to react to throttle user input. For example, when auto-saving text entry, you don’t want to carry out an expensive write operation on every keystroke but perhaps save the text half a second after the user stops typing. (You can use the same pattern for implementing features like auto completion.)
So, you can do, for example:
Swift: var textDidChangeHandler:NotificationHandler?
var cancellableAutoCompleteCommand:CancellableDelayedCommand?
// …
override func viewWillAppear()
{
textDidChangeHandler = handle(NSControlTextDidChangeNotification, from: myTextInput)
{
/* as */ notification in
let text = self.myTextInput.stringValue
// Throttle auto-complete lookups to every 1/3rd of a second.
cancellableAutoCompleteCommand = cancellableAutoCompleteCommand?.reset() ?? delay(0.3)
{
// Perform expensive operation: look-up text for auto-complete
// …
}
}
}
override func viewWillDisappear()
{
cancellableAutoCompleteCommand?.cancel()
textDidChangeHandler?.remove()
}
(For notification handling in the example above, I’m using the Handle micro-library).