Search code examples
iosswiftalamofiresynchronous

How to make a synchronous request using Alamofire?


I am trying to do a synchronous request using Alamofire. I have looked on Stackoverflow and found this question: making an asynchronous alamofire request synchronous.

I saw that the accepted answer uses completion to make Alamofire request synchronous but I cannot make it to work. This is my simplified code:

func loadData(completion: (Bool)) -> (Int, [String], [String], [String]){

    Alamofire.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON { response in

        switch(response.result) {
        case .success(_):
            if let JSON = response.result.value as! [[String : AnyObject]]!{
                 //Here I retrieve the data
            }

            completion(true)
            break

        case .failure(_):
            print("Error")
            completion(false)
            break  
        }
   }

   return (numberRows, nameArray, ageArray, birthdayArray)
}

With this code I am getting an error when trying to make completion(bool value). The error that I am getting is the following:

Cannot call value of non-function type 'Bool'

I have tried using a lot of examples using completion to get the values synchronously (because I need to retrieve the data before to show it on a table and at the same time get the number of rows of that table) without success.

How can I use that completion to get a synchronous response?

Thanks in advance!


Solution

  • Updated:

    You can use a Semaphore to freeze the calling thread until the task has returned a value: Ref

    
    func performSynchronously(request: URLRequest) -> (data: Data?, response: URLResponse?, error: Error?) {
            let semaphore = DispatchSemaphore(value: 0)
    
            var data: Data?
            var response: URLResponse?
            var error: Error?
    
            let task = self.dataTask(with: request) {
                data = $0
                response = $1
                error = $2
                semaphore.signal()
            }
    
            task.resume()
            semaphore.wait()
    
            return (data, response, error)
        }
    
    

    Now, let’s say that we wanted to render the items loaded by the above WWDCItemsLoader within a SwiftUI view. An initial idea on how to do that might be to do something like this: Ref

    struct WWDCItemsList: View {
        var loader: WWDCItemsLoader
        @State private var loadingState = LoadingState<[WWDCItem]>.idle
    
        var body: some View {
            switch loadingState {
            case .idle:
                Color.clear.onAppear(perform: loadItems)
            case .loading:
                ProgressView()
            case .loaded(let items):
                List(items) { item in
                    // Rendering each item
                    ...
                }
            case .failed(let error):
                ErrorView(error: error, reloadHandler: loadItems)
            }
        }
    
        private func loadItems() async {
            loadingState = .loading
            
            do {
                let items = try await loader.load()
                loadingState = .loaded(items)
            } catch {
                loadingState = .failed(error)
            }
        }
    }
    

    Old Answer: (Swift 2.0)

    when you use completion handler do not use return.

    func loadData(completion: @escaping (_ number: Int, _ strArr1: [String], _ strArr2: [String], _ strArr3: [String]) -> ()){
    
      Alamofire.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON { response in
        
        switch(response.result) {
        case .success(_):
            if let JSON = response.result.value as! [[String : AnyObject]]!{
                //Here I retrieve the data
            }
            completion(number: numberRows, strArr1 : nameArray, strArr2 : ageArray, strArr3: birthdayArray)
            break
            
        case .failure(_):
            print("Error")
            completion(number: numberRows, strArr1 : nameArray, strArr2 : ageArray, strArr3: birthdayArray)
            break
        }
      }
    }
    
    loadData (completion: { (number, strArr1, strArr2, strArr3) in
        // do it
        // for exapmple
        self.number = number
        self.strArr1 = strArr1
        // and so on
        
    })
    

    or if you want return any value in closure you must use completion handler for return any value or some thing like, for example if you want return Boolean value:

    func loadData(completion:(number: numberRows, strArr1 : nameArray, strArr2 : ageArray, strArr3: birthdayArray) -> (Bool))
    

    and in the loadData

    loadData( completion: { ( number, strArr1, strArr2, strArr3 ) -> (Bool) in
           # code 
           return False
    })
    

    or some think else.

    I use swift 3. but if you want another version of swift careful about External Parameter Names and internal parameter names, like: @escaping (_ number: Int, _ strArr1: [String], _ strArr2: [String], _ strArr3: [String]) -> ())

    if you want set external parameter names, just need drop _ and set name for parameters.