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

Swift 不理解此代码中的完成处理程序,swift,completionhandler,Swift,Completionhandler,“完成(项目)”的目的是什么?我知道,当我们不知道一个操作什么时候才能完成时(比如下载时间),就会使用这些工具。但是,在本例中,loadData()只是从项目目录中提取一个plist文件,所以我觉得这是一个固定的时间,不需要完成处理程序。另外,我的课本上说它返回annotations数组,但我没有看到任何return语句。我是swift的新手,所以如果这不是一个好问题,我道歉 func fetch(completion: (_ annotations: [RestaurantItem]) -&g

“完成(项目)”的目的是什么?我知道,当我们不知道一个操作什么时候才能完成时(比如下载时间),就会使用这些工具。但是,在本例中,loadData()只是从项目目录中提取一个plist文件,所以我觉得这是一个固定的时间,不需要完成处理程序。另外,我的课本上说它返回annotations数组,但我没有看到任何return语句。我是swift的新手,所以如果这不是一个好问题,我道歉

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

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

那么您知道函数参数如何接受
String
s、
Int
s等

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,因为闭包本身不会返回任何内容)。注释:部分是不必要的:只需执行以下操作:

func fetch(completion: ([RestaurantItem]) -> ()) {
}
调用函数时,需要传入闭包,并将输入分配给变量

fetch(completion: { restaurantItems in
    /// do something with restaurantItems (assigned to the input)
})
您将在
func fetch(completion:(u.annotations:[RestaurantItem])->())的末尾调用此闭包

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

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

那么您知道函数参数如何接受
String
s、
Int
s等

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,因为闭包本身不会返回任何内容)。注释:
部分是不必要的:只需执行以下操作:

func fetch(completion: ([RestaurantItem]) -> ()) {
}
调用函数时,需要传入闭包,并将输入分配给变量

fetch(completion: { restaurantItems in
    /// do something with restaurantItems (assigned to the input)
})
您将在
func fetch(completion:(u.annotations:[RestaurantItem])->())的末尾调用此闭包

调用
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,想法都是一样的。异步请求完成时,我们称完成处理程序闭包为
completion

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

调用方(可能是视图控制器)承担更新模型和UI的责任,之后调用
completion
closure时:

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”消息之前看到“finishing viewDidLoad”消息。但是我们提供给
fetch
的闭包会更新模型并触发重新加载UI

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

但是,我们使用这种复杂的闭包模式的唯一原因是,
fetch
执行的任务是异步运行的。如果
fetch
没有执行异步操作(在您的示例中似乎没有),我们根本不会使用此闭包模式。您只需返回结果


那么,让我们回到你的例子

有几个问题:

  • 更新
    项并返回结果(无论是直接返回还是使用闭包)是没有意义的。你可以做一个或另一个,但不能同时做两个。因此,我可能建议您创建一个局部变量,并在闭包中传递结果(与上面的异步模式非常相似)。例如:

    func fetch(completion: (_ annotations: [RestaurantItem]) -> ()) {
        var items: [RestaurantItem] = []
        for data in loadData() {
            items.append(RestaurantItem(dict: data))
        }
        completion(items)
    }