【问题标题】:How to make a synchronous request using Alamofire?如何使用 Alamofire 发出同步请求?
【发布时间】:2017-02-19 00:52:39
【问题描述】:

我正在尝试使用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'的值

我尝试使用很多示例使用完成来同步获取值(因为我需要先检索数据才能将其显示在表上,同时获取该表的行数)但没有成功。

如何使用该完成来获得同步响应?

提前致谢!

【问题讨论】:

标签: ios swift alamofire synchronous


【解决方案1】:

更新:

您可以使用信号量来冻结调用线程,直到任务返回一个值: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)
    }

现在,假设我们想要在 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)

当你使用完成处理程序时,不要使用 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]) -&gt; ())

如果要设置外部参数名称,只需删除_并设置参数名称。

【讨论】:

  • 非常感谢!你给了我解决我问题的线索! :)
  • 你明白了。我很高兴能帮上忙。;-)
  • 这仍然是异步的......因此 { response in } 块......这基本上是回调
  • @Koen 实际上没有办法将异步更改为同步请求。您可以通过委派或更接近或类似的方式来处理它。
  • 这是异步的,你用了回调,不知道为什么这个答案被接受了。
【解决方案2】:

请注意,出于here 指出的原因,Apple 非常不鼓励发出同步请求。

在这个例子中我是在简化调用,如果你有更多的信息,比如单元格的内容,我建议你看一下 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)

    }
}

【讨论】:

  • 睡眠功能也不会阻塞UI?
  • 会的,我用它来模拟同步调用。同步网络请求会阻塞主线程,这就是强烈建议不要这样做的原因。除非你让它发生在一个单独的线程上(这会给你带来其他问题,比如无法取消请求等),否则你最好让它异步。
  • 问题是,由于我需要在显示表之前检索表的行数,因此我需要使其同步(或者至少是我认为我可以专注的唯一方法在)。如果我异步执行,我会得到该表将有 0 行。
  • 下次在你的描述中充分解释问题,给我几分钟,将编辑我的答案以展示我如何处理这个问题。
  • 我认为您需要在完成处理程序中添加 table.reload()。
【解决方案3】:

您可以使用以下方法将任何方法转换为同步:

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

【讨论】:

    猜你喜欢
    • 2016-08-19
    • 1970-01-01
    • 2018-05-22
    • 2020-08-13
    • 2019-01-25
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2012-06-02
    相关资源
    最近更新 更多