如何使用Alamofire进行同步请求?

17

我正在尝试使用Alamofire进行同步请求。我在Stackoverflow上查看了这个问题:making an asynchronous alamofire request synchronous

我看到接受的答案使用completion使Alamofire请求同步,但我无法使其工作。这是我的简化代码:

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)
}

使用这段代码时,当我尝试创建completion(bool value)时出现错误。我收到的错误消息如下:

无法调用非函数类型'Bool'

我已经尝试使用很多示例来使用完成处理程序同步获取值(因为我需要在显示表格之前检索数据并同时获取该表的行数),但都没有成功。

如何使用完成处理程序获得同步响应?

提前致谢!


我建议您实现自己的方法。苹果公司强烈反对同步网络调用。请看这个链接:https://dev59.com/KFwZ5IYBdhLWcg3wWfOl - FredLoh
请查看此博客:https://github.com/Alamofire/Alamofire/issues/1147。希望这能对您有所帮助。 - Mandeep Singh
1
似乎你不理解异步方法的工作原理,特别是完成回调。与其强制同步调用,不如将精力投入到理解异步编程中。 - vikingosegundo
3个回答

2

更新:

你可以使用信号量来冻结调用线程,直到任务返回一个值:参考


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)
    }

现在,假设我们想要在SwiftUI视图中呈现上述WWDCItemsLoader加载的项目。一个最初的想法是像这样做:

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)
        }
    }
}

旧答案:(Swift 2.0)

当您使用完成处理程序时,请勿使用返回。

新答案: (Swift 2.0)
在使用完成处理程序时,不要使用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
    
})

如果您想在闭包中返回任何值,您必须使用完成处理程序来返回任何值或类似的内容,例如,如果您想返回布尔值:

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

loadData

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

还是其他什么。

我使用的是Swift 3。但如果你想要另一个版本的Swift,请注意外部参数名和内部参数名,例如:@escaping (_ number: Int, _ strArr1: [String], _ strArr2: [String], _ strArr3: [String]) -> ())

如果您想设置外部参数名,只需要删除_并为参数设置名称即可。


2
这仍然是异步的...因此需要 { response in } 块...这基本上就是回调。 - Koen
@Koen 实际上没有办法将异步请求改为同步请求。你可以通过委托、闭包或其他方式来处理它。 - Ali ZahediGol

1
您可以使用以下方法将任何方法转换为同步方法:
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)
}

  1. 创建信号量
  2. 调用等待函数
  3. 异步执行请求
  4. 在完成时调用 semaphore.signal。
- Stephan Januar

0
请注意,由于苹果公司的原因,强烈不建议进行同步请求,详情请参考此处
在这个例子中,我将调用过程简化了,如果您有更多的信息,例如单元格的内容,建议您查看SwiftyJSON并返回整个JSON Blob,然后在相关方法(numberOfRows等)中解析它。
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)

    }
}

睡眠函数也不会阻塞用户界面吗? - Francisco Romero
问题在于,由于我需要在表格显示之前检索表格的行数,所以我需要使其同步(或者至少是我认为可以专注的唯一方式)。如果我异步执行它,那么表格将会有0行。 - Francisco Romero
1
我认为你需要在完成处理程序中添加table.reload()。 - Ali ZahediGol
不,我的代码中使用sleep函数来模拟打电话的行为,请不要在你的代码中添加它。 - FredLoh
你基本上想要尽快分叉主线程,然后尽可能晚地回到主线程。默认情况下在主线程返回的库对此没有帮助。通常你仍然需要处理数据、将其存储到缓存中,然后应用业务逻辑,才能展示它。 - Lucas van Dongen
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接