Reputation: 13199
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!
Upvotes: 17
Views: 19358
Reputation: 1106
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.
Upvotes: 2
Reputation: 2479
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)
}
Upvotes: 1
Reputation:
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)
}
}
Upvotes: 0