首页 > 解决方案 > Swift 中的异步线程 - 如何处理?

问题描述

我正在尝试从 URL 中恢复数据集(在通过parseJSON函数解析 JSON 后,该函数正常工作 - 我没有将它附加到下面的代码段中)。

结果返回nil - 我相信这是因为retrieveData函数中的闭包是异步处理的。我无法将结果保存到targetData中。

在此先感谢您的帮助。

class MyClass {
    
    var targetData:Download?
    
    func triggerEvaluation() {
        retrieveData(url: "myurl.com") { downloadedData in
            self.targetData = downloadedData
        }
        print(targetData) // <---- Here is where I get "nil"!
    }
    
    func retrieveData(url: String, completion: @escaping (Download) -> ()) {
        let myURL = URL(url)!
        let mySession = URLSession(configuration: .default)
        let task = mySession.dataTask(with: myURL) { [self] (data, response, error) in
            if error == nil {
                if let fetchedData = data {
                    let safeData = parseJSON(data: fetchedData)
                    completion(safeData)
                }
            } else {
                //
            }
        }
        task.resume()
    }
}

标签: swiftasynchronousurlsessionnsurlsessiondatatask

解决方案


是的,这是nil因为异步运行,即在您点击语句retrieveData时尚未检索到数据。printprint语句(可能还有 UI 的所有更新)移动到闭包中,就在您设置的位置self.targetData)。

例如

func retrieveData(from urlString: String, completion: @escaping (Result<Download, Error>) -> Void) {
    let url = URL(urlString)!
    let mySession = URLSession.shared
    let task = mySession.dataTask(with: url) { [self] data, response, error in
        guard 
            let responseData = data,
            error == nil, 
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode
        else {
            DispatchQueue.main.async {
                completion(.failure(error ?? NetworkError.unknown(response, data))
            }
            return
        }

        let safeData = parseJSON(data: responseData)
        DispatchQueue.main.async {
            completion(.success(safeData))
        }
    }
    task.resume()
}

在哪里

enum NetworkError: Error {
    case unknown(URLResponse?, Data?)
}

然后调用者会:

func triggerEvaluation() {
    retrieveData(from: "https://myurl.com") { result in
        switch result {
        case .failure(let error):
            print(error)
            // handle error here

        case .success(let download):
            self.targetData = download
            // update the UI here
            print(download)
        }
    }
    // but not here
}

一些不相​​关的观察:

  • 您不想URLSession为每个请求创建一个新的。只创建一个并将其用于所有请求,或者shared像我上面那样使用。

  • 确保每条执行路径都retrieveData调用闭包。它可能还不是很关键,但是当我们编写异步代码时,我们总是希望确保我们调用了闭包。

  • 为了检测错误,我建议使用Result上面显示的模式,它在哪里.success.failure,但无论哪种方式,您都知道将调用闭包。

  • 确保模型更新和 UI 更新发生在主队列上。通常,我们会将retrieveData对闭包的调用分派到主队列,这样调用者就不会受到这些的阻碍。(例如,这就是像 Alamofire 这样的库所做的。)


推荐阅读