簡體   English   中英

Swift 中的閉包:它們是什么? 它們是如何工作的?

[英]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 中的sortsorted是有區別的。 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")
}

現在,您將看到,一旦點擊按鈕,它將調用該函數,但它不會等待服務器返回任何內容,因為沒有closureusers將始終為空。 它將首先打印“這是我的用戶”,然后一段時間后您會看到它會打印“這些是我的用戶(所有用戶)”。

因此,為了解決這個問題,我們需要添加回調或閉包。 具有諷刺意味的是, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM