import XCTest
@testable import TestWait
class TestWait: XCTestCase {
func testX() {
guard Thread.isMainThread else {
fatalError()
}
let exp = expectation(description: "x")
DispatchQueue.main.async {
print("block execution")
exp.fulfill()
}
print("before wait")
wait(for: [exp], timeout: 2)
print("after wait")
}
}
Output:
before wait
block execution
after wait
I'm trying to rationalize the sequence of the prints. This is what I think:
wait
for the expectation to get fulfilled. This wait sleeps the current thread, ie the main thread for 2 seconds.So how in the world does wait succeed even though we still haven't dispatched off of main thread. I mean "after wait" isn't printed yet! So we must still be on main thread. Hence the "block execution" never has a chance to happen.
What is wrong with my explanation? I'm guessing I it must be something with how wait
is implemented
The wait(for:timeout:)
of XCTestCase
is not like the GCD group/semaphore wait
functions with which you are likely acquainted.
When you call wait(for:timeout:)
, much like the GCD wait
calls, it will not return until the timeout expires or the expectations are resolved. But, in the case of XCTestCase
and unlike the GCD variations, inside wait(for:timeout:)
, it is looping, repeatedly calling run(mode:before:)
until the expectations are resolved or it times out. That means that although testX
will not proceed until the wait
is satisfied, the calls to run(mode:before:)
will allow the run loop to continue to process events (including anything dispatched to that queue, including the completion handler closure). Hence no deadlock.
Probably needless to say, this is a feature of XCTestCase
but is not a pattern to employ in your own code.
Regardless, for more information about how Run Loops work, see the Threading Programming Guide: Run Loops.