【问题标题】:Asynchronous thread in Swift - How to handle?Swift 中的异步线程 - 如何处理?
【发布时间】:2021-02-26 13:13:00
【问题描述】:

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

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

【问题讨论】:

  • 谢谢队友-我应该在闭包内使用 DispatchQueue.main.async 吗?即:func triggerEvaluation(){retrieveData(url:“myurl.com”){Dispatchqueue.main.async中的下载数据{self.targetData =下载数据打印(目标数据)}}}
  • 还有:如何将填充的“targetData”变量传递给视图?我应该采用协议委托结构吗?我在问,如果我只是创建这个类的一个实例并从视图访问该属性,它的结果是 nil (假设出于相同的原因)。谢谢!
  • “如何将填充的“targetData”变量传递给视图?” ...通常您只需将数据提供给视图,从而更新必要的 UI 控件。不需要协议委托,但如果视图允许用户输入,那么该模式可能非常有用,并且您需要将用户输入数据反映回模型、Web 服务等。

标签: swift asynchronous urlsession nsurlsessiondatatask


【解决方案1】:

是的,它是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 这样的库所做的。)

【讨论】:

    猜你喜欢
    • 2013-09-10
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-11
    • 2016-05-16
    相关资源
    最近更新 更多