这段代码中的 'DispatchQueue.main.async' 和 'completed: @escaping () -> ()' 是什么意思?

What does 'DispatchQueue.main.async' and 'completed: @escaping () -> ()' mean in this snippet of code?

基本上这是一个简单的项目,涉及一个表视图,该表视图根据从 JSON 从 api 解析的数据进行更新。我相信 DispatchQueue.main.asynccompleted: @escaping () -> () 与 updating/reloading tableview 有关,但我不确定它是如何工作的。如果能解释一下这两者的作用,我们将不胜感激。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var heroes = [HeroStats]()

    override func viewDidLoad() {
        super.viewDidLoad()
        fetchData {
            print("Success")
        }
    }

    func fetchData(completed: @escaping () -> ()) {

        let jsonUrlString = "https://api.opendota.com/api/heroStats"
        guard let url = URL(string: jsonUrlString) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, error) in

            guard let data = data else { return }

            if error == nil {
                do {
                    let heroes = try JSONDecoder().decode([HeroStats].self, from: data)
                    DispatchQueue.main.async {
                        completed()
                    }
                } catch let error {
                    print(error)
                }
            }
        }.resume()        
    }
}

两个问题:

  1. 属性heroesdo块中的局部变量heroes不是同一个对象。您必须将 let 关键字替换为 self

    self.heroes = try JSONDecoder().decode([HeroStats].self, from: data)
    

    并且因为 dataTask 可以创建一个保留循环添加 [unowned self] 来避免这种情况:

    URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in ...
    
  2. 在完成块中,您必须重新加载 table 视图。

    fetchData() {
        print("Success")
        tableView.reloadData()
    }
    

语法 DispatchQueue.main.async 正确。 sync 会阻塞线程。

注意:您可以删除勾选if error == nil。如果guard成功,error保证是nil. 最好检查错误而不是data

if let error = error { 
   print(error)
   return 
}

 do {
    self.heroes = try JSONDecoder().decode([HeroStats].self, from: data!)
    ...
  1. DispatchQueue.main.async { ... } 表示 "run this following code inside the braces on the main queue"(所有 UI 更新必须 运行)。 URLSession 闭包和委托方法 运行 在专用 URLSession 队列上,但 UI (和模型)更新应该发生在主队列上。

    仅供参考,将代码分派到另一个队列的两种常见方法是 asyncsync。它们非常相似,但是 async 运行 是异步的(即 async 调用之后的代码不会等待主线程完成调用 completed 后再继续) , 和 sync 运行s 同步(即它会等待)。在这种情况下,让 URLSession 队列等待主队列完成是没有意义的,因此 async 是合适的。

  2. completed: @escaping () -> ()表示:

    • fetchData有一个参数,叫做completed
    • 此参数是 "closure"(即调用者可以提供的匿名代码位;有关详细信息,请参阅 The Swift Programming Language: Closures);
    • 这个闭包本身不带任何参数,也不带任何参数returns;和
    • 这个闭包"escapes"(意味着到fetchData方法returns时它不一定是运行)。

    因此,您可以传递要调用的代码块(当您在 dataTask 闭包中看到 completed() 时),如下所示:

    fetchData(completed: {
        print("Success")
        self.tableView.reloadData()  // make sure to reload table when request is done
    })
    

    但是,您的示例使用 "trailing closure" 语法,其中最终闭包(在本例中是唯一的闭包)可以作为参数省略,并在 fetchData 调用之后添加,导致相同的行为(但语法更简洁):

    fetchData {
        print("Success")
        self.tableView.reloadData()
    }
    

    或者,甚至更好:

    fetchData { [weak self] in
        print("Success")
        self?.tableView.reloadData()
    }
    
  3. 在一个不相关的观察中,你永远不会更新你的 heroes 属性。至少,你应该这样做:

    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data, error == nil else { 
            print(error ?? "Unknown error")
            return 
        }
    
        do {
            let heroes = try JSONDecoder().decode([HeroStats].self, from: data)
            DispatchQueue.main.async {
                self.heroes = heroes
                completed()
            }
        } catch let error {
            print(error)
        }
    }.resume()
    

    请注意,您要更新 self.heroes 属性 inside async 闭包以确保您不更新属性 来自后台线程。数组不是线程安全的,通过在主线程上更新 属性,可以避免任何竞争条件。

    我可以建议许多其他改进(在 dataTask 中使用 weakself 的引用;向您的 completed 闭包添加一个参数,以便调用者知道是否它成功了,如果不成功则显示警告等),但以上是我建议的最低限度。