“This is the 10th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Most of the time, we want our various asynchronous tasks to start as soon as they are created, but sometimes we may want to delay their execution slightly — perhaps to give another task time to complete first, or to add some form of “de-jitter” behavior.

Although there is no direct, built-in way to run a Swift task with some latency, we can do this behavior by putting the task to sleep for a given number of nanoseconds before actually starting to execute its operation:

The reason the above call to task.sleep is marked with the try keyword is that it raises an error if the Task is canceled during its sleep. So, for example, if we wanted the view controller to display the load spinner only when the asynchronous operation took more than 150 milliseconds to complete, we could implement something like this:

Now, if we’re going to use a lot of deferred tasks in a given code base, it might be worth defining a simple abstraction to make it easier to create such deferred tasks — for example allowing us to define a second based delay using a more standard TimeInterval value, rather than having to use nanoseconds:

With the extension above, we can now simply call task.delayed when we want to create a delayed Task. The only drawback to this approach is that we now have to manually capture the self in these task closures:

There is, however, a way around this little problem — and that is to use the “semi-public” _implicitSelfCapture attribute — which the Swift standard library uses to make all built-in Task closures automatically capture self-references:

However, since the above attribute is not an official part of Swift’s public API (given its name is prefixed with an underscore), I really don’t recommend using it in production code — unless you’re willing to accept the risk that any code that uses it could break at any time.

I hope you enjoyed this quick look at several different ways to model delayed actions using Swift’s new built-in task API. Of course, we can also choose to implement this delay using older tools — such as Grand Central Dispatch, timers, or even objective-C runtimes (at least in some cases). However, when writing asynchronous code using Swift’s new concurrent system, it is very convenient to be able to implement this deferred behavior directly using the Task type.