在此代码段中,“ DispatchQueue.main.async”和“ completed:@escaping()->()”是什么意思?

Jas*_*ark 0 closures grand-central-dispatch ios swift

基本上,这是一个简单的项目,涉及一个表视图,该表视图根据从api的JSON解析的数据进行更新。我相信DispatchQueue.main.asynccompleted: @escaping () -> ()有事情做与更新/重装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()        
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 5

  1. DispatchQueue.main.async { ... }意思是“运行在主队列中的括号内这下面的代码”(其是所有UI更新必须运行)。URLSession闭包和委托方法在专用URLSession队列上运行,但UI(和模型)更新应在主队列上进行。

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

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

    • 有一个参数fetchData称为completed;
    • 该参数是一个“闭包”(即调用者可以提供的匿名代码;有关更多信息,请参见Swift编程语言:闭包);
    • 这种闭包本身不带任何参数,也不返回任何东西;和
    • 此闭包“转义”(意味着它不一定在fetchData方法返回时运行)。
       

    因此,您可以像下面这样传递代码块(completed()dataTask闭包中看到时):

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

    但是,您的示例使用了“跟踪闭包”语法,其中可以将最终闭包(在这种情况下,唯一的闭包)作为参数省略,并在fetchData调用之后添加,导致相同的行为(但语法更简洁) :

    fetchData {
        print("Success")
        self.tableView.reloadData()
    }
    
    Run Code Online (Sandbox Code Playgroud)

    或者,甚至更好:

    fetchData { [weak self] in
        print("Success")
        self?.tableView.reloadData()
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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()
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,您要更新的self.heroes属性里面async封闭,以确保您不更新从后台线程属性。数组不是线程安全的,并且通过在主线程上更新该属性,可以避免任何竞争情况。

    我可以建议其他许多改进(使用in的weak引用;向您的闭包中添加一个参数,以便调用者知道它是否成功,如果失败则显示警告,等等),但是以上是我的最低要求建议。selfdataTaskcompleted