5 Easy Dispatch Tricks

Swift Dispatch offers a great way to schedule and control concurrent code. Here are five easy ways to improve your Dispatch experience.

1. Adding Seconds

Swift makes it easy to fetch the current time as DispatchTime.

let timeNow = DispatchTime.now()

You can easily add seconds to now (in Double increments) using the + operator. As you can see from its signature, the RHS of the + operator expects a Double value of seconds.

Here are a couple of examples that show how easy it is to do this:

let timeInFive = DispatchTime.now() + 5
let timeInSevenAndHalf: DispatchTime = .now() + 7.5

DispatchTime also supports the minus operator, although subtraction is rarely used in dispatch:

public func -(time: DispatchTime, seconds: Double) -> DispatchTime

2. Adding unit time

DispatchTimeInterval offers handy units for microseconds, milliseconds, nanoseconds, and seconds. They all take integer arguments:

For example, you might dispatch five print commands, each offset by an increasing number of seconds:

(1 ... 5).forEach {
    DispatchQueue.main.asyncAfter(
        deadline: .now() + .seconds($0)) {
        print("Hi there!")
    }
}

This approach works by mixing and matching DispatchTime and DispatchTimeInterval math. These operators, too, are baked into Swift:

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime
public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

This version is arguably more readable and maintainable than the raw + 5 from trick #1:

let timeInFive = DispatchTime.now() + .seconds(5)

3. Going Floating Point with Dispatch Time Intervals

DispatchTimeInterval case initializers are limited to integer arguments. You can extend the type to add support for double values, returning a nanosecond instance with the equivalent value for a Double number of seconds:

// Built-in enumeration
public enum DispatchTimeInterval {
 case seconds(Int)
 case milliseconds(Int)
 case microseconds(Int)
 case nanoseconds(Int)
}

// Custom factory using a `Double` value
extension DispatchTimeInterval {
    public static func seconds(_ amount: Double) -> DispatchTimeInterval {
        let delay = Double(NSEC_PER_SEC) * amount
        return DispatchTimeInterval.nanoseconds(Int(delay))
    }
}

Like the earlier use of DispatchTimeInterval this convenience constructor improves code readability while bypasssing Int-only case initializers:

let timeInEightAndHalf: DispatchTime = .now() + .seconds(8.5)

4. Async Forwards

Although DispatchTime math is convenient, it’s super-common to schedule code with respect to the current time. Why not let DispatchTime do the heavy lifting for you? Instead of saying .now() + some interval, consider extending DispatchTime to  incorporate now into the call. Here’s an example that introduces a secondsFromNow(_:) dispatch time:

extension DispatchTime {
    public static func secondsFromNow(_ amount: Double) -> DispatchTime {
        return DispatchTime.now() + amount
    }
}

stride(from: 1.0, through: 5.0, by: 1.0).forEach {
    DispatchQueue.main.asyncAfter(deadline: .secondsFromNow($0)) {
        print("Hi there!")
    }
}

5. Testing in Playgrounds

When working with dispatch in Playgrounds, ensure that execution  continues until your work is done. Use PlaygroundPage‘s indefinite execution and halting to control that work.

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) {
    print("Ending Execution")
    PlaygroundPage.current.finishExecution()
}

Got any other favorite dispatch tricks? See something I need to fix? Drop me a comment, a tweet, or an email and let me know.

Update:

Like this post? Consider buying a book. Thank you!

2 Comments

  • Shouldn’t ‘+’ take a (NS)TimeInterval instead of a Double?

    • You can file a bug at bugs.swift.org