Swift 5.5 and Xcode 13.0 bring async/await, so you can do something like this:
import Foundation
print("Hello, World!")
// Returns a 1 after a 3-second delay
func giveMeAOne() async -> Int {
Thread.sleep(forTimeInterval: 3.0)
return 1
}
// Returns a 5 after a 3-second delay
func giveMeAFive() async -> Int {
Thread.sleep(forTimeInterval: 3.0)
return 5
}
func myFunction() async {
var a: Int = 0
print(a)
a += await giveMeAOne()
print(a)
a += await giveMeAFive()
print(a)
}
async {
await myFunction()
}
sleep(7) // added at the end so that process doesn't exit right away
Note that the above was built as a macOS command-line tool in Xcode, rather than with Swift Playgrounds. As of Xcode 13.0 beta 1, Swift Playgrounds fail on a lot of async/await stuff, unfortunately.
That code above runs them sequentially, and cleans up myFunction quite a bit. Note that this doesn't introduce any concurrency, however. Both giveMeAOne() and giveMeAFive() are run sequentially.
If you wanted to run 4 function calls concurrently, you could do something like this:
/// Returns a random integer from 1 through 10, after a 6-second delay
func randomSmallInt() async -> Int {
Thread.sleep(forTimeInterval: 6)
return Int.random(in: 1 ... 10)
}
/// Fetches 4 random integers, summing them
func myFunction() async {
// print timestamp
print(Date())
async let a = randomSmallInt()
async let b = randomSmallInt()
async let c = randomSmallInt()
async let d = randomSmallInt()
// This awaits for a, b, c, and d to all complete.
// You can only await a value once.
let numbersToAdd = await [a, b, c, d]
var sum = 0
for number in numbersToAdd {
print("adding \(number)")
sum += number
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async {
await myFunction()
}
// make sure we have time to finish before process exits
Thread.sleep(forTimeInterval: 10)
When you run this, you can see from the timestamps that the total execution time only took about 6 seconds -- because all of the async let ...
statements were executed concurrently.
This works great if you have a fixed number of things you need to call asynchronously. But what if you wanted to make 100 asynchronous function calls, perhaps loading images or something?
You may think you could do something like this, which will work, but will run everything synchronously -- probably not what you want:
// Don't do this -- these all run sequentially
var sum = 0
for _ in 0 ..< 100 {
async let value = randomSmallishInt()
let number = await value
print("Adding \(number)")
sum += number
}
If you want to do an arbitrary or unknown number of asynchronous things, the best way it to use a taskGroup
. This code is a bit more verbose than it needs to be, in order to make things clearer. Note: Xcode 13.0 beta struggles with respect to print
statement output, missing much of it. Anyway, here it is:
/// Returns a random integer from 1 through 10
func randomSmallInt() async -> Int {
Thread.sleep(forTimeInterval: 6)
return Int.random(in: 1 ... 10)
}
func myFunction_arbitrary_repetitions_asynchronously() async {
// print timestamp
print(Date())
// This is pretty cool --
// `withTaskGroup` creates a context for executing a group of async tasks
// In this case, `Int.self` specifies that each individual task will return
// an Int. `[Int].self` specifies that the entire group will return an array of Ints:
let numbers = await withTaskGroup(of: Int.self, returning: [Int].self) { group in
// Repeat 100 times
for _ in 0 ..< 100 {
// Run the next thing asynchronously
group.async {
return await randomSmallInt()
}
}
// Iterate through results
var result: [Int] = []
for await individualResult in group {
result.append(individualResult)
}
return result
}
var sum = 0
for number in numbers {
print("Adding \(number)")
sum += number
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async { await myFunction_arbitrary_repetitions_asynchronously() }
Thread.sleep(10)
You could shorten this up considerably:
func myFunction_arbitrary_repetitions_asynchronously_short() async {
// print timestamp
print(Date())
// In this case, the taskGroup just returns the sum
let sum = await withTaskGroup(of: Int.self, returning: Int.self) { group in
// Repeat 100 times
for _ in 0 ..< 100 {
// Run the next thing asynchronously
group.async {
return await randomSmallInt()
}
}
// Iterate through results
var sum = 0
for await individualResult in group {
print("Adding \(individualResult)")
sum += individualResult
}
return sum
}
print("Sum = \(sum)")
// print another timestamp, so you can see that the async let statements ran
// concurrently, rather than sequentially
print(Date())
}
async { await myFunction_arbitrary_repetitions_asynchronously_short() }
Important: Xcode 13.0 beta 1 gets quite unstable when dealing with this code. Specifically, the debugger likes to detach when the process isn't finished yet. For example, the output of many of the print
statements does not appear, and using breakpoints is tough with async code. Building a command-line tool and then running the resulting executable (found in ~/Library/Developer/Xcode/DerivedData//Build/Products/Debug) will at least show all the output of your print statements.