Delay

18th April, 2015 — Aral Balkan

Long-exposure of a clockface
Timing is everything (or at least very important) in crafting experiences.
Photo courtesy Janet Ramsden

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).