问题描述
“完成(项目)”的目的是什么?我了解到,当我们不知道操作何时完成(如下载时间)时,将使用这些功能。但是,在这种情况下,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)
)
那么您知道函数参数如何接受String
,Int
等吗?
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
个结果即可。
所以,让我们回到您的示例。
有几个问题:
-
更新
return
并返回结果(无论是直接返回还是使用闭包)都没有意义。您可能会做一个或另一个,但不能两者都做。因此,我可能建议您创建一个局部变量,然后将结果传递给闭包(非常类似于上面的异步模式)。例如:items
-
我可以使用
func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) { var items: [RestaurantItem] = [] for data in loadData() { items.append(RestaurantItem(dict: data)) } completion(items) }
进一步简化此操作,例如:map
-
无论您执行上述哪一项操作,都可以做到:
func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) { let items = loadData().map { RestaurantItem(dict: $0)) } completion(items) }
-
但这是非常误导的。如果您看到一个名称为
func viewDidLoad() { ... fetch { items in self.items = items } }
且带有结束符的方法,那么以后的读者就会以为这是一个异步方法(这是我们采用该模式的唯一原因)。如果是同步的,我可以简化为fetch
个结果:return
和
func fetch() -> [RestaurantItem] { return loadData().map { RestaurantItem(dict: $0)) } }
不用说,如果func viewDidLoad() {
...
items = fetch()
}
是异步的,那么您将将使用fetch
闭包,如我的答案开头所示。这是典型的关闭示例。