[英]Closures in Swift: what are they? how do they work?
我正在閱讀一本關於 Swift 基礎知識的書,關於閉包的部分對我來說意義不大。
這是他們提供的示例:
要對數組進行排序以使小數排在大數之前,您可以提供一個描述如何進行排序的閉包,如下所示:
var numbers = [2,1,56,32,120,13]
// Sort so that small numbers go before large numbers
var numbersSorted = numbers.sort({
(n1: Int, n2: Int) -> Bool in return n2 > n1
})
// = [1, 2, 13, 32, 56, 120]
我不明白在這種情況下閉包的目的是什么——即使沒有閉包, numbers.sort()
也不會返回相同的值嗎? 另外,我不確定閉包在這里實際上是在做什么。
更廣泛地說,從這個例子中我不確定你什么時候可以在 Swift 中使用閉包。 從精神上講,它們似乎接近於減少函數,但我不確定您何時會使用它們。
編輯:此代碼示例在我使用的 Swift 游樂場(v. 12.0)中不起作用也無濟於事。 它抱怨對sort
的調用在 call 中Missing argument label 'by:' in call
。 也許那里有更好的關閉示例。
用非常簡單的術語來說,閉包是一個可以傳遞的函數。
關於使用排序,您可以選擇對對象的不同屬性進行排序,因此您可能希望創建自己的自定義排序函數/閉包。
請注意,Swift 中的sort
和sorted
是有區別的。 sort
改變原始數組, sorted
創建數組的副本。 所以在你的例子中numbersSorted
不會有一個值,如果你希望它有一個值,你應該使用sorted
。
let numbers = [2, 5, 3, 2, 1, 0, -8, 12]
let numbersSorted = numbers.sorted { first, second -> Bool in
return first < second
}
print(numbers) // [2, 5, 3, 2, 1, 0, -8, 12]
print(numbersSorted) // [-8, 0, 1, 2, 2, 3, 5, 12]
但是我不需要像那樣編寫上面的代碼。 我可以把閉包換成一個函數。
let numbers = [2, 5, 3, 2, 1, 0, -8, 12]
func ascending(first: Int, second: Int) -> Bool {
first < second
}
let numbersSorted = numbers.sorted(by: ascending)
print(numbers) // [2, 5, 3, 2, 1, 0, -8, 12]
print(numbersSorted) // [-8, 0, 1, 2, 2, 3, 5, 12]
請注意,函數ascending
與我們之前使用的閉包具有相同的簽名。
或者我可以寫一個閉包作為一個變量,然后可以傳遞
let numbers = [2, 5, 3, 2, 1, 0, -8, 12]
let ascending = { (first: Int, second: Int) in
return first < second
}
let numbersSorted = numbers.sorted(by: ascending)
print(numbers) // [2, 5, 3, 2, 1, 0, -8, 12]
print(numbersSorted) // [-8, 0, 1, 2, 2, 3, 5, 12]
最重要的是函數和閉包的簽名匹配。 這樣他們就可以互換了。
我個人更喜歡使用第二個選項,因為它使代碼更具可讀性並且看起來更干凈。
當談到閉包時,還有一個你必須面對的術語,那就是@escaping
。 這意味着什么?
@escaping
閉包比它傳遞給的函數的@escaping
。 基本上是說這段代碼應該在調用函數后執行。
這在網絡調用時很常見,因為它們可能需要一段時間才能完成。 該函數將執行,但您還沒有收到響應,一旦響應返回,它就會執行完成塊(我們的閉包),我們可以看到更新。
這里有一些不錯的文章,你可以跟進
https://learnappmaking.com/closures-swift-how-to/
https://www.hackingwithswift.com/example-code/language/what-is-a-closure
https://www.swiftbysundell.com/basics/closures/
https://www.hackingwithswift.com/example-code/language/what-is-an-escaping-closure
一個沒有閉包的網絡請求,我們舉一個從服務器獲取用戶的例子:
func getUsers() -> Users { //Users is coddle
guard let url = URL(string:"https://your-url.com") else {
print("Invalid Url")
return
}
let request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { [weak self](data, response, error) in
if let error = error { print( error) }
if let data = data {
//Get your JSON from data here. I user decodable so, I will return
guard let users = try? JSONDecoder().decode(Users.self) else { return }
print("These are my users", users)
return users
}
}
現在,在上面的示例中,您正在請求服務器為您提供用戶記錄,這是一個GET
請求。 當您點擊此請求時,您希望獲得用戶記錄,但它會失敗。 就像你在@IBAction
函數中編寫它一樣,你會看到:
func btnTap(_ sender: UIButton) {
let users = getUsers()
print("Here are my users")
}
現在,您將看到,一旦點擊按鈕,它將調用該函數,但它不會等待服務器返回任何內容,因為沒有closure
, users
將始終為空。 它將首先打印“這是我的用戶”,然后一段時間后您會看到它會打印“這些是我的用戶(所有用戶)”。
因此,為了解決這個問題,我們需要添加回調或閉包。 具有諷刺意味的是, URLSession.shared.dataTask(with: request) { [weak self](data, response, error) in
本身就是一個閉包。
現在,具有閉包的相同功能:
func getUsersWithCallback(completion: @escaping(_ users: [Users], _ error: Error?) -> Void) -> { //Users is codable
guard let url = URL(string:"https://your-url.com") else {
print("Invalid Url")
completion(nil, nil) //Handle this
return
}
let request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { [weak self](data, response, error) in
if let error = error {
print( error)
completion(nil, error) //Now, this is a callback, once you receive an error from the server it will pass the error here and this function will return a value only once the completion is achieved or we pass a call back.
}
if let data = data {
//Get your JSON from data here. I user decodable so, I will return
guard let users = try? JSONDecoder().decode([Users].self) else { return }
print("These are my users", users)
completion(users, nil) //Now this will return the users
}
}
現在,這是一個上面有閉包的例子,通過添加閉包我們告訴函數等待。 有一些東西來自服務器,我們現在依賴於服務器來返回響應,所以我們現在等待服務器返回響應。 以前,我們不是在等待服務器響應。
現在,我們的@IBAction
將獲得值:
func btnTap(_ sender: UIButton) {
let users = getUsersWithCallback( [weak self] (users, error) in
print("Users", users)
}
print("Here are my users")
}
現在,您將獲得所有用戶的值,因為它們現在是從服務器收到的,我們已經等了足夠多的時間才能收到回電。 所以,這就是閉包。 同樣的事情是用sort
完成的,要對上面的數組進行排序,它必須將一個值與所有剩余的值進行比較,一旦完成,它就會為您提供數組。
為了理解閉包參數的用途,假設您是實現Array.sort
的人可能會有所幫助。 你可以從寫這樣的東西開始(注意:這不是真正實現Array.sort
方式):
struct Array<T> {
...
mutating func sort() { // bubble sort
guard self.count > 1 else {
return
}
for i in 0..<self.count {
for j in 0..<(self.count - 1) - i {
if (self[j + 1] < self[j]) { // compare the elements
self.swapAt(j, j + 1)
}
}
}
}
...
}
然后你意識到上面的代碼有錯誤。 在比較元素的行中,您意識到<
不能應用於任何隨機T
。 T
必須符合Comparable
才能使用<
比較它們。 所以你認為,好吧,只有當T
符合Comparable
,我才會使這種sort
方法可用。 這正是numbers.sort()
沒有閉包的情況下有效的原因,因為numbers
是一個Int
數組,而Int
符合Comparable
。
然后你想,很多其他類型都不是Comparable
,但是對它們也有一個sort
方法會很好。 人們可以“告訴我”他們希望如何比較它們。 此外,對於Comparable
類型,一些用戶可能不喜歡將它們與<
進行比較,而是與>
進行比較。 我也希望他們“告訴我”這一點。
因此,您用函數調用替換了>
:
if (shouldComeBefore(self[j + 1], self[j])) { // compare the elements
並添加了一個參數來sort
:
mutating func sort(by shouldComeBefore: (T, T) -> Bool) {
所述參數是一個函數,它有兩個T
S作為參數,並返回一個Bool
。 此返回值指示第一個T
是否應該在排序數組中的第二個T
之后。
現在讓我們嘗試調用sort
:
func descendingOrder(n1: Int, n2: Int) -> Bool {
return n1 > n2
}
numbers.sort(by: descendingOrder)
現在您應該看到通過執行numbers.sort(by: descendingOrder)
,我們“告訴” sort
使用>
進行比較,而不是<
。
每次我們調用這種sort
時都要編寫一個函數,這很麻煩,這就是我們有閉包語法的原因。 同樣的事情可以寫成:
numbers.sort(by: { (n1: Int, n2: Int) -> Bool in return n1 > n2 })
很明顯, descendingOrder
函數是如何轉換為上述閉包的。 閉包可以進一步簡化為:
numbers.sort { $0 > n1 }
有關如何簡化閉包語法的更多信息,請參閱 Swift 指南中的 閉包。
當我像您一樣第一次使用 sort 函數時,我感到很困惑,因為在我看來,特別難以理解使用這種 sort 函數傳遞數據。 您基本上有 3 次傳遞數據的機會:通知、協議和閉包。 首先考慮一下,請簡單地構建一個簡單的項目(添加按鈕、標簽等)並嘗試而不是操場:
class AVC: UIViewController{
// some codes
@IBAction func firstButtonClick(_ sender: Any) {
let sb = UIStoryboard(name: "Main", bundle: nil)
if let destVC = sb.instantiateViewController(withIdentifier: "BVC") as? BVC {
destVC.successListener = { hello in
let sb = UIStoryboard(name: "Main", bundle: nil)
if let destVC = sb.instantiateViewController(withIdentifier: "CVC") as? CVC {
destVC.helloStr = hello
self.navigationController?.pushViewController(destVC, animated: true)
}
}
self.present(destVC, animated: false, completion: nil)
}
}
另一方面,讓我們看看 destVC(目標視圖控制器)
class BVC: UIViewController{
typealias SuccessListener = (String) -> ()
var successListener: SuccessListener?
//some codes
@IBAction func secondButtonClick(_ sender: Any) {
self.dismiss(animated: false, completion: {
let str = "Hello World"
self.successListener?(str)
})
}
因此,當您觸發第二個按鈕時,您應該考慮兩個閉包:第一個是在dismiss 函數及其完成中。 當您調用dismiss函數時,它會說嘿,您也在完成中執行此操作。 因為它是像 () -> () 這樣的完成意味着不帶參數並且不返回任何內容。 第二個是你創建的成功監聽器閉包,它有區別 (String) -> () 將字符串作為參數,不返回任何內容。 現在回到我的例子你可以將任何字符串作為參數傳遞給成功監聽器,在我的例子中它是“str”。 它可以做飯了。
destVC.successListener = { hello in
這部分無論你寫哪一個 hello hola ciao 都會傳遞“Hello World”的 str
希望它會有所幫助
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.