不了解此代码中的完成处理程序

问题描述

“完成(项目)”的目的是什么?我了解到,当我们不知道操作何时完成(如下载时间)时,将使用这些功能。但是,在这种情况下,loadData()只是从项目目录中拉出一个plist文件,所以我觉得那是一个恒定的时间,看不到需要完成处理程序。另外,我的教科书上说它返回了注解数组,但是我看不到任何return语句。我是新手,所以如果这不是一个好问题,我深表歉意。

func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
        if items.count > 0 { items.removeAll() }
        for data in loadData() {
            items.append(RestaurantItem(dict: data))
        }
        completion(items)
    }

解决方法

是的,带有完成处理程序的函数不需要return语句-只需调用完成处理程序(completion(items)

那么您知道函数参数如何接受StringInt等吗?

func doSomething(inputThing: Int) {
                             ^ this is the type (an Int)
}

他们也可以接受闭包。在您的示例中,completion参数接受闭包。

func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
                       ^ this is the type (a closure)
}

基本上,闭包是您可以传递的代码块。通常,如果函数接受闭包作为参数,则将闭包称为“完成处理程序”(因为它通常在函数的结尾被调用)。

您的闭包还指定了[RestaurantItem]类型的输入和() (Void)的输出(无效,因为闭包本身不会返回任何内容)。 _ annotations:部分是不必要的:只需执行以下操作:

func fetch(completion: ([RestaurantItem]) -> ()) {
}

调用函数时,您需要传递闭包,并将输入分配给变量。

fetch(completion: { restaurantItems in
    /// do something with restaurantItems (assigned to the input)
})

您将在func fetch(completion: (_ annotations: [RestaurantItem]) -> ())的结尾处调用此闭包。

func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
    if items.count > 0 { items.removeAll() }
    for data in loadData() {
        items.append(RestaurantItem(dict: data))
    }
    completion(items) /// call the closure!
    /// this is a completion handler because you called it at the end of the function
}

调用completion(items)items传递到闭包的输入中,闭包的输入已分配给restaurantItems

通常,闭包用于需要一些时间才能运行的功能,例如下载文件。但是在您的示例中,loadData()看起来会立即发生,因此您应该使用带有返回类型的普通函数。

func fetch() -> [RestaurantItem] {
    if items.count > 0 { items.removeAll() }
    for data in loadData() {
        items.append(RestaurantItem(dict: data))
    }
    return items
}

let restaurantItems = fetch()
,

我们通常在编写异步代码时使用完成处理程序闭包,即在那些我们开始耗时的情况下(例如,网络请求),但是您不想在调用时阻塞调用者(通常是主线程)这种相对较慢的网络请求正在发生。

因此,让我们看一个典型的完成处理程序模式。假设您正在使用URLSession进行异步网络请求:

func fetch(completion: @escaping ([RestaurantItem]) -> Void) {
    let task = URLSession.shared.dataTask(with: url) { data,response,error in
        // parse the `data`
        let items: [RestaurantItem] = ...
        DispatchQueue.async { completion(items) }
    }
    task.resume()
}

(我使用URLSession作为异步过程的示例。很明显,如果您使用的是Alamofire或Firebase或任何异步API,则想法是相同的。我们将完成处理程序闭包称为{{1 }},异步请求完成后。)

这将启动网络请求,但立即返回,并且稍后网络请求完成时将调用completion。请注意,completion不应直接更新模型。它只是将结果提供给闭包。

您的调用者(也许是视图控制器)负责在稍后调用fetch闭包时更新模型和UI:

completion

如果我们观看控制台,则会在“获得项目”消息之前看到“正在完成viewDidLoad”消息。但是我们提供给var items: [RestaurantItems] = [] // start with empty array override func viewDidLoad() { super.viewDidLoad() fetch { items in print("got items",items) self.items = items // this is where we update our model self.tableView.reloadData() // this is where we update our UI,a table view in this example } print("finishing viewDidLoad") } 的闭包完成了模型的更新并触发了UI的重新加载。

这是一个过于简化的示例,但这是完成处理程序关闭的基本思想,它允许我们提供可以在完成一些异步任务时执行的代码块,同时允许fetch立即返回这样我们就不会阻止用户界面。

但是,我们经历这种复杂的关闭模式的唯一原因是因为fetch执行的任务是异步运行的。如果fetch并没有执行异步操作(在您的示例中似乎没有这样做),我们根本不会使用这种关闭模式。您只需fetch个结果即可。


所以,让我们回到您的示例。

有几个问题:

  1. 更新return并返回结果(无论是直接返回还是使用闭包)都没有意义。您可能会做一个或另一个,但不能两者都做。因此,我可能建议您创建一个局部变量,然后将结果传递给闭包(非常类似于上面的异步模式)。例如:

    items
  2. 我可以使用func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) { var items: [RestaurantItem] = [] for data in loadData() { items.append(RestaurantItem(dict: data)) } completion(items) } 进一步简化此操作,例如:

    map
  3. 无论您执行上述哪一项操作,都可以做到:

    func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
        let items = loadData().map { RestaurantItem(dict: $0)) }
        completion(items)
    }
    
  4. 但这是非常误导的。如果您看到一个名称为func viewDidLoad() { ... fetch { items in self.items = items } } 且带有结束符的方法,那么以后的读者就会以为这是一个异步方法(这是我们采用该模式的唯一原因)。如果是同步的,我可以简化为fetch个结果:

    return

    func fetch() -> [RestaurantItem] {
        return loadData().map { RestaurantItem(dict: $0)) }
    }
    

不用说,如果func viewDidLoad() { ... items = fetch() } 是异步的,那么您将将使用fetch闭包,如我的答案开头所示。这是典型的关闭示例。