Using Dispatch.main in code called from XCTestCase does not work - swift

I have a function that is a async wrapper around a synchronous function.
The synchronous function is like:
class Foo {
class func bar() -> [Int] {
return [1,2,3]
}
class func asyncBar(completion: #escaping ([Int]) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let intArray = bar()
DispatchQueue.main.async {
completion(intArray)
}
}
}
}
When I call it from a XCTestCase completion does not run.
Is there some sort of perverse interaction between the way unit tests are done in XCode and the main thread?
I can find no documentation on the Web about this.
I have to use the main thread for the callback as it interacts with the Gui.
My test case looks something like:
func testAsyncBar() throws {
var run = true
func stopThisThing(ints: [Int]) {
run = false
}
Foo.asyncBar(completion: stopThisThing)
while run {
print ("Running...")
usleep(100000)
}
}
The busy loop at the end never stops.

Your test’s while loop will block the main thread (if the test runs on the main thread). As such, the closure dispatched via DispatchQueue.main.async can never run, and your run Boolean will never get reset. This results in a deadlock. You can confirm this by printing Thread.isMainThread or adding a test like dispatchPrecondition(condition: .onQueue(.main)).
Fortunately, unit tests have a simple mechanism that avoids this deadlock.
If you want unit test to wait for some asynchronous process, use an XCTestExpectation:
func testAsyncBar() throws {
let e = expectation(description: "asyncBar")
func stopThisThing(ints: [Int]) {
e.fulfill()
}
Foo.asyncBar(completion: stopThisThing)
waitForExpectations(timeout: 5)
}
This avoids problems introduced by the while loop that otherwise blocked the thread.
See Testing Asynchronous Operations with Expectations.

Related

How to run an #MainActor func synchronously?

I have an #MainActor function that I want to run synchronously. So far, I've come up with this:
static func runOnMainThreadAndWait<T>(
action: #MainActor #Sendable () throws -> T
) rethrows -> T {
if Thread.isMainThread {
return try unsafeExecuteWithoutActorChecking(action)
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
#preconcurrency
private static func unsafeExecuteWithoutActorChecking<T>(
_ action: () throws -> T
) rethrows -> T {
return try action()
}
This works, but the first case of that if statement feels janky, and with -warn-concurrency even unsafeExecuteWithoutActorChecking(_:) doesn't suffice:
Converting function value of type '#MainActor #Sendable () throws -> T' to '() throws -> T' loses global actor 'MainActor'
What's the safest way to implement this call?
Edit, for more details:
The function is usually called from the main thread -- specifically, when a view is created -- and the return value is needed to render the views. It was originally written under the assumption that this would be the only way it was called, so no safety mechanisms were put in place. I have been going back through the code and adding #MainActor annotations and similar safety checks where appropriate. Here, I need to support the common case (calling it synchronously while on the main thread) while making the incorrect behavior (running on a background thread) impossible. I added an asynchronous overload that calls await MainActor.run ... as a stopgap measure, but most of the time awaiting it is unnecessary and only adds noise.
I've seen a variation on this solution from before Swift's structured concurrency:
static func runOnMainThreadSync<T>(action: () throws -> T) rethrows -> T {
if Thread.isMainThread {
return try action()
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
and I thought I could just add #MainActor to this.
Running something in the #MainActor context does not mean that you are running on the main thread.
It does mean that your code will run synchronously relative to other code running on the Main Actor, which includes code running in the main thread. But all that means is that while your code is running in the main actor context, if it's not running on the main thread, then the main thread will be suspended. (no other main actor code will be running in parallel)
So your if check is not just janky, it's bogus.
You don't say much about the calling context where you are trying to run something on the main actor, but the easiest solution is probably to use
await MainActor.run {
... some code here ...
}
You said:
I've seen a variation on this solution from before Swift's structured concurrency:
static func runOnMainThreadSync<T>(action: () throws -> T) rethrows -> T >{
if Thread.isMainThread {
return try action()
} else {
return try DispatchQueue.main.sync(execute: action)
}
}
and I thought I could just add #MainActor to this.
You can (and simplify it significantly):
extension MainActor {
#MainActor static func runOnMainThread<T>(action: #MainActor #Sendable () throws -> T) rethrows -> T {
try action()
}
}
But I would advise against this. One could use MainActor.run.
Or, even better, we can just mark the methods that must be run on a particular actor, and the compiler will perform compile-time checks (rather than the above run-time checks). If you call it from a context not isolated to the particular actor the compiler will tell you that you have to await. And only if you have a series of await suspension points to the main actor and would like to wrap that in a single task would you ever need to use MainActor.run.
But with Swift concurrency, there is no guessing, like we might have had to do in GCD. Nowadays, this runOnMainThreadSync pattern (anti-pattern?) is unnecessary. In Swift concurrency, we are generally explicit about which methods and properties are actor isolated, eliminating the ambiguity.

Wait for main queue being dispatched before continue unit test

I have this method in a class :
func handleResp(_ dataGotten: Result<Response?, Error>) {
....
DispatchQueue.main.async {
....
GlobalStaticStruct.code = 7
...
}
}
}
And then in my unit testing I have this code :
func testhandleResp() {
subject = SomeClass()
subject.handleResp(successResponse)
XCTAssertNotNil(GlobalStaticStruct.code)
}
I need to be able to test that GlobalStaticStruct.code is not nil but with the code as it is the test completes before the main queue so the field is nil all the time.
I understand the use of expectations to overcome similar issues but in this case there is no handler I can hook up to.
How can I make my test to wait for the main method to get triggered and finished and THEN test the value of GlobalStaticStruct.code?
Thanks in advance!
Specifically for main queue, you can use the fact that it's a serial queue. So if you schedule your check to run on the same queue after handleResp, your check will pass. And you can use expectation to synch the test after that check:
func testhandleResp() {
subject = SomeClass()
subject.handleResp(successResponse)
let expectation = XCTestExpectation()
DispatchQueue.main.async { // scheduled after subject.handleResp
XCTAssertNotNil(GlobalStaticStruct.code)
expectation.fulfill()
}
wait(for: [expectation], timeout: 10.0)
}
If it was any other (possibly concurrent) thread, you could do the same, but schedule verification block to run later, using schedule(after:...

How to include completion handlers for multiple functions in swift?

Consider this code:
func test() {
A()
B()
C()
D()
E()
}
Each function here have their own set of actions like calling API's, parsing them, writing results in file, uploading to servers, etc etc.
I want to run this functions one by one. I read about completion handlers. My problem with completion handlers are:
All examples given to understand completion handlers was having just two methods
I don't want to place this functions in some other function. I want all function calls (A to E) inside Test() function alone
Can someone help on this?
This is easy to do, you just need to add a closure argument to call on completion. For example:
func a(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func b(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func c(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func d(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func e(completion: (() -> Void)) {
// When all async operations are complete:
completion()
}
func test() {
a {
b {
c {
d {
e {
// All have now completed.
}
}
}
}
}
}
As you can see, this looks horrible. One problem with multiple async operations, non concurrently is that you end up with this horrible nesting.
Solutions to this do exist, I personally recommend PromiseKit. It encapsulates blocks in easy chaining methods, which is far cleaner.
Take a look at reactive programming and observable chains. RxSwift will be an excellent library for you. Reactive programming is very popular right now and was created exactly to solve problems like yours. It enables you to easily create a process (like an assembly line) that transforms inputs into your desired outputs. For example, your code in Rx would look like:
A()
.flatMap { resultsFromA in
B(resultsFromA)
}
.flatMap { resultsFromB in
C(resultsFromB)
}
.flatMap { resultsFromC in
D(resultsFromC)
}
.flatMap { resultsFromD in
E(resultsFromD)
}
.subscribe(onNext: { finalResult in
//result from the process E, subscribe starts the whole 'assembly line'
//without subscribing nothing will happen, so you can easily create observable chains and reuse them throughout your app
})
This code sample would create a process where you'd transform results from your initial API call (A), into parsed results (B), then write parsed results to a file (C) etc. Those processes can be synchronous or asynchronous. Reactive programming can be understood as Observable pattern on steroids.

PromiseKit firstlyOn style method

PromiseKit provides a convenience method thenOn for running parts of your chain on non-main threads. However there doesn't appear to be any convenient way of setting the first promise execution thread.
This means that I end up either placing a DispatchQueue.global(x).async inside my first promise, or I use a dummy first promise.
Placing the DispatchQueue bit in my first promise feels broken, I'm moving the threading decision from the main execution chain to the individual promise, but just for that one promise. If I later prepend a promise to my chain, I have to move all that threading logic around... not good.
What i've been doing lately is this:
let queue = DispatchQueue.global(qos: .userInitiated)
Promise(value: ()).then(on: queue) {
// Now run first promise function
}
This is definitely a cleaner solution, but I was wondering if anyone knew of an
even better solution... I'm sure this isn't a rare scenario after all?
We provide:
DispatchQueue.global().promise {
//…
}
firstly does not take a queue because it executes its contents immediately for the default case and thus allowing it to have a configured queue would make its behavior confusingly variable.
UPDATE: At the latest version it looks like
DispatchQueue.global().async(.promise) {
//...
}
You can perform your operation on any queue, just call fulfill or reject from it
Look at example:
override func viewDidLoad() {
super.viewDidLoad()
firstly {
doFirstly()
}
.then {
self.doMain()
}
.then {
self.doLastest()
}
.catch { error in
// handle error
}
.always {
// finish
}
}
func doFirstly() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
// do your operation here
fulfill()
}
}
}
func doMain() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.main
queue.async {
// do your operation here
fulfill()
}
}
}
func doLastest() -> Promise<Void> {
return Promise { fulfill, reject in
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
// do your operation here
fulfill()
}
}
}

How to cancel a Dispatch Queue

I have a method that has this block inside:
func a(_ foo: () -> ()) { ... }
func b(_ foo: () -> ()) { ... }
func abc() {
a {
// some processing
b {
// some asynchronous work
}
}
}
When a button is tapped:
I call method abc()
It connects to the internet
The point is that it takes time to do so
I am looking for a way to cancel the previous block, and run the current block if tapped twice.
There is no direct option to cancel you're block. And block will be executed when it is called only one way to set condition in the body of the block and do not execute part that is inside.
In you're case try to use Operation and OperationQueue this approach will give you flexible solution to mange operation execution and gives opportunity to cancel operations.
Small example:
let operationQueue = OperationQueue.main
let operation = Operation()
operationQueue.addOperation(operation)
//Cancel operation that is executing
operation.cancel()