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



"完成(项目)"的目的是什么?我知道这些是在我们不知道什么时候会像下载时间一样完成操作时使用的。然而,在这种情况下,loadData()只是从项目目录中提取一个plist文件,所以我觉得这是一个恒定的时间,不需要完成处理程序。此外,我的课本上说它返回注释数组,但我没有看到任何返回语句。我是swift的新手,所以如果这不是一个好问题,我道歉。

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

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

因此,让我们来看一个典型的完成处理程序模式。假设您正在使用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,想法也是一样的。当异步请求完成时,我们调用完成处理程序闭包completion。)

这会启动网络请求,但会立即返回,稍后网络请求完成时会调用completion。注意,fetch不应直接更新模型。它只是为结束提供结果。

当稍后调用completion闭包时,您的调用者(可能是视图控制器)将负责更新模型和UI:

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")
}

如果我们观看控制台,我们将在"getitems"消息之前看到"finishingviewDidLoad"消息。但是我们提供给fetch的闭包完成了模型的更新和UI的重新加载。

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

但是,我们采用这种复杂的闭包模式的唯一原因是fetch执行的任务异步运行。如果fetch没有做异步的事情(在您的示例中似乎没有这样做),我们根本不会使用这个闭包模式。你只需要return结果。


那么,让我们回到您的例子。

有几个问题:

  1. 更新items并返回结果(无论是直接返回还是使用闭包)都没有意义。你会做其中一个,但不能两者都做。因此,我可能建议您创建一个局部变量,并在闭包中传递结果(很像上面的异步模式)。例如:

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

    func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
    let items = loadData().map { RestaurantItem(dict: $0)) }
    completion(items)
    }
    
  3. 不管你做上面的哪一个,你都可以做:

    func viewDidLoad() {
    ...
    fetch { items in
    self.items = items
    }
    }
    
  4. 但这是非常误导人的。如果您看到一个名称为fetch的方法带有闭包,那么未来的读者只会认为它是一个异步方法(因为这是我们采用该模式的唯一原因)。如果它是同步的,我会将其简化为return结果:

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

    func viewDidLoad() {
    ...
    items = fetch()
    }
    

不用说,如果fetch是异步的,那么使用@escaping闭包,如我的答案开头所示。这是典型的闭包示例。

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

那么你知道函数参数是如何接受Strings、Ints等的吗?

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

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

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

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

您的闭包还指定了[RestaurantItem]类型的输入和()/Void类型的输出(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()

最新更新