swiftsignalsdispatchsemaphore

async requests on Swift using completion handler and DispatchSemaphore


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"


Solution

  • 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"))")