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

巧了我就是萌 提交于 2019-12-13 03:44:35

问题


Basically this is a simple project that involves a tableview that is updated according to data that is parsed from JSON from an api. I believe DispatchQueue.main.async and completed: @escaping () -> () have something to do with updating/reloading the tableview but I'm not sure how it works. Explanation would be appreciated on what these two do.

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:


  1. The DispatchQueue.main.async { ... } means "run this following code inside the braces on the main queue" (which is where all UI updates must run). URLSession closures and delegate methods run on a dedicated URLSession queue, but UI (and model) updates should happen on the main queue.

    FYI, the two common methods of dispatching code to another queue are async and sync. They're very similar, but async runs asynchronously (i.e. the code after the async call won't wait for the main thread to finish calling completed before continuing), and sync runs synchronously (i.e. it would wait). In this case, there's no point in having the URLSession queue to wait for the main queue to finish, so async is appropriate.

  2. The completed: @escaping () -> () indicates that:

    • there is a parameter to fetchData, called completed;
    • that this parameter is a "closure" (i.e. an anonymous bit of code that the caller can provide; see The Swift Programming Language: Closures for more information);
    • this closure that takes no parameters, itself, nor returns anything; and
    • this closure "escapes" (meaning that it won't necessarily be run by the time the fetchData method returns).
       

    So, you could pass a block of code to be called (when you see completed() in the dataTask closure) like so:

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

    But, your example uses "trailing closure" syntax, where the final closure (and in this case, the only closure) can be omitted as a parameter, and just added after the fetchData call, resulting in the same behavior (but more concise syntax):

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

    Or, even better:

    fetchData { [weak self] in
        print("Success")
        self?.tableView.reloadData()
    }
    
  3. In an unrelated observation, you never update your heroes property. At the very least, you should do:

    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()
    

    Note, you want to update the self.heroes property inside the async closure to make sure that you don’t update the property from a background thread. Arrays are not thread-safe, and by updating that property on the main thread, you avoid any race conditions.

    There are many other improvements I could suggest (use weak reference to self in dataTask; add a parameter to your completed closure so the caller knows if it was successful and display warning if it wasn’t, etc.), but the above is the bare minimum that I’d suggest.




回答2:


Two issues:

  1. The property heroes is not the same object as the local variable heroes in the do block. You have to replace the let keyword with self

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

    and as dataTask can create a retain cycle add [unowned self] to avoid that:

    URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in ...
    
  2. In the completion block you have to reload the table view.

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

The syntax DispatchQueue.main.async is correct. sync would block the thread.

Note: You can delete the check if error == nil. If the guard succeeds error is guaranteed to be nil. It's better to check the error rather than data

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

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


来源:https://stackoverflow.com/questions/48699048/what-does-dispatchqueue-main-async-and-completed-escaping-mean-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!