Swift 中的异步线程 - 如何处理?

Asynchronous thread in Swift - How to handle?

我正在尝试从 URL 恢复数据集(在通过 parseJSON 函数解析 JSON 之后正确 - 我没有在下面的代码片段中附加它。

结果 returns 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()
    }
}

是的,它是 nil,因为 retrieveData 是异步运行的,即在您点击 print 语句时数据尚未检索。将 print 语句(可能还有 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 这样的图书馆所做的。)