17

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!

Community
  • 1
  • 1
Francisco Romero
  • 12,787
  • 22
  • 92
  • 167
  • I would recommend you implement your own method. Apple has highly discouraged synchronous network calls. Take a look at this: http://stackoverflow.com/questions/31557688/synchronous-url-request-on-swift-2 – FredLoh Oct 10 '16 at 15:23
  • https://github.com/Alamofire/Alamofire/issues/1147, you have to see this blog. Hope this will helps you. – Mandeep Singh Oct 10 '16 at 16:20
  • 1
    It seems you dont understand how asynchronous methods works, especially with completion call backs. Instead of forcing synchronous calls, invest that effort into understanding asynchronous programming. – vikingosegundo Oct 10 '16 at 16:36

3 Answers3

2

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.

Ali ZahediGol
  • 876
  • 2
  • 10
  • 20
  • 2
    This is still asynchronous... hence the { response in } block... Which is basically the callback – Koen Jun 29 '20 at 11:35
  • @Koen Actually no way to change the async to sync request. you could handle it with delegation or closer or some way something like that. – Ali ZahediGol Jun 29 '20 at 12:57
1

You can convert any method to synchronous using something like this:

func getName() -> (String?, Error?) { //an async call is in there
    let semaphore = DispatchSemaphore(value: 0)
    var name: String? // result to return
    var error: Error? // error to throw
    service.getUserName().subscribe { result in //call alamofire or anything
        switch(result) {
          case .success(let res): name = res.name
          case .failure(let err): error = err
        }
    } onFailure: { err in
        error = err
    }.disposed(by: bag)
    semaphore.wait()
    return (name, error)
}
Mahdi Moqadasi
  • 2,029
  • 4
  • 26
  • 52
  • This answer put me on the right track. 1. Create Semaphore 2. Call Wait 3. Do your request async 4. In Completion call semaphore.signal – Stephan Januar Jul 26 '22 at 12:00
0

Note that making synchronous requests is highly discouraged by Apple, for reasons noted here.

In this example I'm simplifying the call, if you have more information, such as the content of the cells, I suggest you take a look at SwiftyJSON and return the entire JSON Blob, then parse it in the relevant methods (numberOfRows, etc.).

class TableViewJSONAsynchCalls: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var tableView = UITableView()
    var numberOfRows = 0;

    override func viewDidLoad() {
        loadData { (didCompleteRequest) in
            if (didCompleteRequest) {
                tableView.delegate = self
                tableView.dataSource = self
                tableView.reloadData()
            } else {
                // Handle error if data was not loaded correctly
            }
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return numberOfRows;
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("selected")
    }

    func loadData(completion: (Bool) -> Void) {
        // Make asynchronous call using alamofire
        // This simulates you parsing the JSON and setting the relevant variables, 
        // personally I would recommend you return a JSON blob and then 
        // parse it in the relevant methods.
        sleep(2)
        // If call is successful
        self.numberOfRows = 10
        completion(true)

    }
}
Community
  • 1
  • 1
FredLoh
  • 1,804
  • 18
  • 27
  • sleep function will not block the UI aswell? – Francisco Romero Oct 10 '16 at 15:42
  • It will, I'm using it to simulate a synchronous call. A synchronous network request will block the main thread, which is why it is highly discouraged. Unless you make it happen on a separate thread (which will give you other problems like the inability to cancel the request etc.), at which point you might as well make it asynchronous. – FredLoh Oct 10 '16 at 15:46
  • The problem is that, as I need to retrieve the number of rows of a table before the table is displayed, I need to make it synchronous (or at least is the only way I think I can focus on). If I do it asynchronously, I get that the table will have 0 rows. – Francisco Romero Oct 10 '16 at 15:52
  • Next time explain the problem fully in your description, give me a couple of minutes, will edit my answer to showcase how I handle this. – FredLoh Oct 10 '16 at 15:53
  • I am sorry I thought it was clear enough with the data provided. Should I add it also to the question? – Francisco Romero Oct 10 '16 at 16:04
  • 1
    I think you need add table.reload() in completion handler. – Ali ZahediGol Oct 10 '16 at 16:09
  • Is it necessary to use sleep function? I know that one of the big problems of using synchronous request is that you are going to block the UI but sleep function also does so I do not see a lot of difference between block the UI with asynchronous request or using synchronous request. – Francisco Romero Oct 10 '16 at 16:14
  • No, the sleep function is used in my code to simulate the act of making a call, do not add it in your code. – FredLoh Oct 10 '16 at 16:28
  • You basically want to fork off the main thread as soon as possible then go back to the main thread as late as possible. A library that by default returns on main doesn't help with that. Often you still need to work on the data, store it to caches and then apply business logic before you're going to show it. – Lucas van Dongen May 26 '20 at 12:30