I'd like to test/display behavior in Swift Playgrounds related to a function that mutates values after a delay. For simplicity, let's just say it mutates a string. I know I can delay the execution of updating the value via DispatchQueue.main.asyncAfter
and that I can sleep the current thread using usleep
or sleep
.
However, since the playground is seemingly running in a synchronous thread, I'm not able to see the changes after sleeping.
Here's an example of what I would like to do:
var string = "original"
let delayS: TimeInterval = 0.100
let delayUS: useconds_t = useconds_t(delayS * 1_000_000)
func delayedUpdate(_ value: String) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
string = value
}
}
delayedUpdate("test2")
assert(string == "original")
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test2") // ❌ Assertion failure. string is "original" here
delayedUpdate("test3")
assert(string == "test2") // ❌ Assertion failure. string is "original" here
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test3") // ❌ Assertion failure. string is "original" here
delayedUpdate("test4")
assert(string == "test3") // ❌ Assertion failure. string is "original" here
usleep(delayUS)
print(string) // ❌ Prints "original"
assert(string == "test4") // ❌ Assertion failure. string is "original" here
Notice all the failed assertions since anything at the top level doesn't see the changes to string
. This seems like a synchronous vs. asynchronous thread issue.
I know I can fix it by replacing usleep
with more asyncAfter
:
delayedUpdate("test2")
assert(string == "original")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
print(string)
assert(string == "test2")
delayedUpdate("test3")
assert(string == "test2")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
print(string)
assert(string == "test3")
delayedUpdate("test4")
assert(string == "test3")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayS) {
print(string)
assert(string == "test4")
}
}
}
However, this leads to a pyramid of doom of indented code each time the app is delayed. This is not too bad with 3 levels, but if I have a large playground, this will become really hard to follow.
Is there a way to use something closer to the first linear programming style that respects updates updated after delays?
Another potential solution is to wrap each reference to string
in a asyncAfter
:
delayedUpdate("test2")
assert(string == "original")
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test2") }
delayedUpdate("test3")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test2") }
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test3") }
delayedUpdate("test4")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test3") }
usleep(delayUS)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { print(string) }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.001) { assert(string == "test4") }
However this is not preferred either since it is pretty messy as well, and probably error prone if one execution relies on the previous value of string
to do its function, for example. It also needs a 0.001
or similar correction to ensure there is no race condition.
How do I use a linear programming style (e.g. with sleep
) in a Swift playground, but have values that are updated during the sleep be reflected correctly by the lines after sleep
?