async requests on Swift using completion handler and DispatchSemaphore

1.4k Views Asked by At

I'm trying to make async requests on Swift using Alamofire's completion handler and DispatchSemaphores . I need to get a response and then return it to another method, so basically this is my code

func customRequest(zipCode: String) -> Bool{
    var response = false

    let dispatchQueueProcess = DispatchQueue.global(qos: .userInitiated)
    let semaphore = DispatchSemaphore(value: 1)
    
    dispatchQueueProcess.async {
        
        semaphore.wait()

        apiRequest(zipCode: zipCode) { apiResponse in
        
            if apiResponse != nil {
            
response = apiResponse
               
             
            } else {
                print("Server did not Response")
            }
            
            semaphore.signal()
            
            
        }
    }
    
    return response
}

The problem is that request always returns false because is not waiting until apiRequest responds... Do you have any idea to fix this? Thank you so much!

Ps. apiRequest returns "true"/"false"

2

There are 2 best solutions below

4
On BEST ANSWER

Is your intention here to create a synchronous function that will block and wait until the async request completes?

If so, you're almost there but you need to change the Semaphore to start with 0 rather than 1 (decrementing from 1 to 0 won't stop execution, it needs to go negative), and you need to move the wait() call outside the closure, otherwise you aren't stopping the function from returning and instead would be blocking your closure from ever completing.

func customRequest(zipCode: String) -> Bool {
    var response = false
    
    let dispatchQueueProcess = DispatchQueue.global(qos: .userInitiated)
    let semaphore = DispatchSemaphore(value: 0)
    
    // Start async work on background thread, current function's thread 
    // execution point will then immediately move to the line 
    // after the closing brace 
    dispatchQueueProcess.async {
        apiRequest(zipCode: zipCode) { apiResponse in
            if let apiResponse = apiResponse {
                response = apiResponse
            } else {
                print("Server did not Response")
            }

            // Tell the original function's thread that it is OK to continue
            semaphore.signal()

        }
    }

    // Immediately wait for the semaphore after starting the async work so that
    // the customRequest(zipCode:) function pauses here until the semaphore is
    // signalled in the closure.
    semaphore.wait()
    
    // Once the semaphore.signal() call happens in the async closure execution
    // resumes, and the response variable can now be returned with the updated
    // value.
    return response
}

You might find that you don't actually want to make a synchronous function like this, because if you call this from the main queue you will freeze the UI until the response is received.

Edit: This example should copy-paste run in a Playground

import Foundation

// template function to simulate the API call
func apiRequest(zipCode: String, response: (Bool?)->Void) {
    sleep(3)
    response(true)
}

func customRequest(zipCode: String) -> Bool{
    var response = false
    
    let dispatchQueueProcess = DispatchQueue.global(qos: .userInitiated)
    let semaphore = DispatchSemaphore(value: 0)
    
    dispatchQueueProcess.async {
        apiRequest(zipCode: zipCode) { apiResponse in
            if let apiResponse = apiResponse {
                response = apiResponse
            } else {
                print("Server did not Response")
            }
            semaphore.signal()

        }
    }
    
    semaphore.wait()
    
    return response
}

print("Result: \(customRequest(zipCode: "90030"))")
0
On

Your return statement is outside the completion handler, so it will run immediately, returning it's initial false value, rather than returning the result of the request as you require.

You should delete the response property and deal with the response inside the completion handler.