Swift - 閉包



什麼是 Swift 中的閉包?

閉包是自包含的功能塊,可以在程式內部使用並執行指定的任務。閉包類似於 Objective-C 中的塊或其他程式語言中的匿名函式。它們可以捕獲並存儲對其定義所在上下文中變數或常量的引用。此外,即使它們超出了原始作用域,它們也可以訪問這些常量或變數的值。

Swift 中的閉包有三種形式:

  • 全域性函式 - 它們是有名稱的閉包,不捕獲任何值。

  • 巢狀函式 - 在另一個函式內部定義的函式。它們有名稱,可以捕獲來自封閉函式的值。

  • 閉包表示式 - 使用此方法可以更簡潔地編寫閉包。我們可以編寫未命名的閉包來捕獲相鄰塊中的值。

閉包表示式

閉包表示式提供了一種編寫內聯閉包或內聯和未命名函式的方法。它支援各種簡短、最佳化和集中的語法來編寫閉包,而不會失去其清晰度。因此,我們首先將看到閉包的基本語法,然後我們將轉向 Swift 支援的其他表示式語法:

  • 從上下文中推斷引數和返回值型別。

  • 單表示式閉包的隱式返回。

  • 簡寫引數名

  • 運算子方法

定義和呼叫基本閉包

在 Swift 中,我們可以簡單地使用花括號 {} 來定義閉包。這些花括號包含閉包引數、返回型別(如果可用)、用於分隔引數和返回型別與主體部分的 `in` 關鍵字以及閉包的主體。閉包的引數可以是常規引數、輸入輸出引數和可變引數,但它們不包含預設值。元組也可以用作閉包中的引數和返回型別。我們可以透過為引數傳遞值(如果可用)來呼叫閉包。

語法

以下是定義接受引數並返回資料型別的閉包的通用語法:

{(parameters) -> return type in
   // body of closure
}

以下是呼叫閉包的語法。

closure(parameters)

示例

Swift 程式演示了一個沒有引數的閉包。

// Creating a Closure
let studname = { print("Welcome to Swift 4 Closures") }

// Calling a closure
studname()
輸出

它將產生以下輸出:

Welcome to Swift 4 Closures

示例

Swift 程式演示了一個帶有引數的閉包。

// Closure with parameters
let divide = {(val1: Int, val2: Int) -> Int in 
   return val1 / val2 
}

// Calling closure
let result = divide(200, 20)
print (result)
輸出

它將產生以下輸出:

10

從上下文中推斷型別

閉包也可以作為內聯閉包表示式傳遞到函式或方法中,因此我們可以推斷其引數和返回值的型別。這意味著我們不需要顯式編寫閉包中傳遞的引數型別和閉包返回的值型別,編譯器將根據閉包的使用上下文自動推斷閉包的型別。

示例

Swift 程式將閉包作為引數傳遞給函式。

// Define an array of String 
let myValues = ["Mohina", "Suman", "Mohit"]

// Use the 'map' function to add the given string in all the elements of the array 
/* The type of closure is inferred according to the fact that 'map()' is 
applied to an array of strings. So here the closure adds a specified string to each element
hence the inferred type of the closure is (String) -> String*/
let newArray = myValues.map { $0 + " Hey" }

print(newArray) 
輸出

它將產生以下輸出:

["Mohina Hey", "Suman Hey", "Mohit Hey"]

單表示式閉包的隱式返回

在閉包中,單一表達式可以隱式地返回一個表示式,而無需顯式使用 `return` 關鍵字。或者我們可以說,如果閉包只包含一條語句,則它可以返回一個表示式而無需指定返回型別。這使語法更簡潔易讀。

示例

Swift 程式從單表示式閉包隱式返回表示式。

// Single line closure without return type
let add: (Int, Int) -> Int = { a, b in
   a + b
}

let output = add(5, 6)
print("Addition:", output) 
輸出

它將產生以下輸出:

Addition: 11

簡寫引數名

在使用內聯閉包時,我們可以使用 $0、$1、$2 等名稱來編寫閉包引數的值,而不是為它們命名。這是在閉包中表達引數的最短方法。其中 $0 指的是第一個引數,$1 指的是第二個引數,$2 指的是第三個引數,依此類推。

如果我們使用這些簡寫引數名,那麼我們可以從定義部分移除閉包引數列表。編譯器將根據預期的函式型別自動推斷引數的型別。我們還可以移除 `in` 關鍵字,因為簡寫引數是在表示式主體中定義的。

示例

Swift 程式演示了閉包中的簡寫引數名。

// Creating a closure
var shorthand: (String, String) -> String

// Assigning the second parameter and discarding the first parameter
shorthand = { $1 }

// Calling the closure with two arguments will return the second parameter
print(shorthand("100", "200"))
輸出

它將產生以下輸出:

200

運算子方法

Swift 提供了一種簡單的方法來訪問成員,只需將運算子函式作為閉包即可。或者我們可以說,使用閉包,我們可以透過過載它們來定義運算子的行為。

示例

Swift 程式演示了閉包中的運算子方法。

// Define a custom operator method for addition numbers 
func + (left: (Double, Double), right: (Double, Double)) -> (Double, Double) {
   return (left.0 + right.0, left.1 + right.1)
}

// Using the custom operator in a closure
let addNumbers: ((Double, Double), (Double, Double)) -> (Double, Double) = { $0 + $1 }

let num1  = (3.0, 3.0)
let num2 = (5.0, 2.0)

// Adding the values using addNumbers closure
let result = addNumbers(num1, num2)
print("Resultant Sum: \(result)")
輸出

它將產生以下輸出:

Resultant Sum: (8.0, 5.0)

尾隨閉包

尾隨閉包是 Swift 中的一種特殊型別的閉包。當閉包定義在函式括號 () 之外時,特別是當閉包是函式的最後一個引數時,這種型別的閉包被稱為尾隨閉包。

這種型別的閉包通常用於閉包很長且無法內聯編寫的情況。如果函式只包含閉包作為引數,那麼在呼叫函式或方法時,我們可以移除括號 (),例如,`names.map{$1 = $0}`。

語法

以下是尾隨函式的語法:

// Function that takes closure
func functionName(closure:()->void){
  // Function body
}

// Calling function without trailing closure
functionName(closure:{// closure body})

// Calling function with trailing closure
functionName(){// closure body}

示例

Swift 程式演示了尾隨閉包。

// Function to operate on two numbers using a trailing closure
func operation(_ x: Int, _ y: Int, op: (Int, Int) -> Int) -> Int {
   return op(x, y)
}

// Using trailing closure to add two numbers
let res = operation(8, 9) { (a, b) in
   return a + b
}
print("Sum: \(res)")

輸出

它將產生以下輸出:

Sum: 17

尾隨函式在高階函式(如 map、filter 或 sort)中很常見,其中閉包充當回撥或轉換。

示例

Swift 程式演示了高階函式中的尾隨閉包。

// Array of string
let names = ["Mohan", "Mohit", "Roy", "Suman"]

// Calling map() function with trailing function
let lowercaseNames = names.map { $0.lowercased() }

// Displaying the names in lowercased
print(lowercaseNames) 

輸出

它將產生以下輸出:

["mohan", "mohit", "roy", "suman"]

單個函式可以有多個尾隨閉包,我們可以移除第一個尾隨閉包的引數標籤,併為其餘的尾隨閉包新增標籤。

示例

Swift 程式演示了函式中的多個尾隨閉包。

// Function with multiple trailing closures
func Operations(_ x: Int, _ y: Int, op1: (Int, Int) -> Int, op2: (Int, Int) -> Int) -> (Int, Int) {
   let result1 = op1(x, y)
   let result2 = op2(x, y)
   return (result1, result2)
}

// Using multiple trailing closures
var output = Operations(11, 6, op1: { $0 + $1 }, op2: { $0 * $1 })

print(output)

輸出

它將產生以下輸出:

(17, 66)

捕獲值和引用型別

在 Swift 中,捕獲常量和變數值是透過閉包實現的。它進一步引用和修改閉包主體中這些常量和變數的值,即使定義變數或常量的原始作用域不再存在。

將函式或閉包賦值給常量或變數時,我們將該常量或變數設定為對該函式或閉包的引用。這意味著如果我們將閉包賦值給兩個常量或變數,則這兩個常量或變數都引用同一個閉包。

示例

func calcDecrement(forDecrement total: Int) -> () -> Int {
   var overallDecrement = 100
   func decrementer() -> Int {
      overallDecrement -= total
      print(overallDecrement)
      return overallDecrement
   }
   return decrementer
}
let decrem = calcDecrement(forDecrement: 18)
print(decrem())
print(decrem())
print(decrem())

輸出

它將產生以下輸出:

82
82
64
64
46
46

每次呼叫外部函式 `calcDecrement` 時,它都會呼叫 `decrementer()` 函式,將值減 18 並透過外部函式 `calcDecrement` 返回結果。這裡 `calcDecrement` 充當閉包。

即使函式 `decrementer()` 沒有引數,閉包預設也會透過捕獲其現有值來引用變數 `overallDecrement` 和 `total`。指定變數的值的副本將與新的 `decrementer()` 函式一起儲存。Swift 透過在變數不用時分配和釋放記憶體空間來處理記憶體管理函式。

廣告