Swift之閉包(Closure)

本文首發於個人我的博客html

什麼是閉包

  • 一個函數和它所捕獲的變量\常量環境組合起來,稱爲閉包
    • 通常指定義在函數內部的函數
    • 通常它捕獲的是外層函數的局部變量\常量
  • 能夠把閉包想象成是一個類的實例對象
    • 內存在堆空間
    • 捕獲的局部變量\常量就是對象的成員(存儲屬性)
    • 組成閉包的函數就是類內部定義的方法

eg: 咱們有一個函數 sumgit

// 函數
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
// 使用
sum(10, 20)
複製代碼

若是用閉包表達式定義一個函數github

var fn = {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}

// 使用
fn(10, 20)
複製代碼

固然了,也能夠編程

{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(10, 20)

複製代碼

總結起來就是swift

{
    (參數列表) -> 返回值類型 in 函數體代碼
}
複製代碼

閉包表達式的簡寫

咱們定義以下的函數 exec ,它接收三個參數,分別爲兩個Int 和一個函數,並且這個函數,接收兩個Int 參數,返回一個Int結果,exec 的做用就是,把前兩個參數傳給第三個參數(也就是函數)去執行,而後結果打印出來數組

// 函數 咱們定義以下的函數 exec ,它接收三個參數,分別爲兩個Int 和一個函數,並且這個函數,接收兩個Int 參數,返回一個Int結果,exec 的做用就是,把前兩個參數傳給第三個參數(也就是函數)去執行,而後結果打印出來
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}
複製代碼

若是用閉包表達式來定義的話bash

// 閉包表達式
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})
複製代碼

固然了,咱們能夠省略不少,以下閉包

// 省略參數類型 由於swift能夠本身推斷類型
exec(v1: 10, v2: 20, fn: {
    v1, v2 in return v1 + v2
})

// return 也能夠省略
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2
})

// 省略掉參數列表,用$0表明第0個參數,$1表明第1個參數
exec(v1: 10, v2: 20, fn: {
    $0 + $1
})

// 終極省略
exec(v1: 10, v2: 20, fn: +)
複製代碼

尾隨閉包

  • 若是將一個很長的閉包表達式做爲函數的最後一個實參,使用尾隨閉包能夠加強函數的可讀性
    • 尾隨閉包是一個被書寫在函數調用括號外面(後面)的閉包表達式

有以下的函數 閉包表達式做爲函數的最後一個實參app

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}
	
複製代碼

使用尾隨閉包爲函數

exec(v1: 10, v2: 20) {
    $0 + $1
}
複製代碼
  • 若是閉包表達式是函數的惟一實參,並且使用了尾隨閉包的語法,那就不須要在函數名後邊寫圓括號
// 這個閉包表達式表達式是函數的惟一實參
func exec(fn: (Int, Int) -> Int) { 
	print(fn(1, 2))
}

複製代碼

可使用尾隨閉包以下

// 使用尾隨閉包以下三種均可以
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }

複製代碼

尾隨閉包實戰

系統自帶的排序

假設咱們有個包含Int元素的數組,想對立面的元素進行排序

func numberSort()  {
    var arr = [6, 8, 1, 10]
    arr.sort()
    print(arr) //[1, 6, 8, 10]
}

numberSort()

複製代碼

打印結果爲

[1, 6, 8, 10]

查看官方對sort的源碼爲

// 官方代碼
func sort(by areInIncreasingOrder: (Element, Element) -> Bool)
複製代碼

自定義排序

假如咱們想自定義排序

/// 返回true: i1排在i2前面
/// 返回false: i1排在i2後面
func cmp(i1: Int, i2: Int) -> Bool {
    // 大的排在前面
    return i1 > i2
}
複製代碼

使用的時候

var nums = [6, 8, 1, 10]
nums.sort(by: cmp)
print(nums)
複製代碼

打印結果爲

[10, 8, 6, 1]

用尾隨閉包書寫

上面的代碼

能夠寫成

nums.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 > i2
})
複製代碼

也能夠等價於下面幾種

nums.sort(by: { i1, i2 in return i1 > i2 })
nums.sort(by: { i1, i2 in i1 > i2 })
nums.sort(by: { $0 > $1 })
nums.sort(by: > )
nums.sort() { $0 > $1 }
nums.sort { $0 > $1 }
複製代碼

忽略參數

Swift中,不少時候,若是咱們對於參數不作處理,能夠用 下劃線 _ 來代替

例以下面的閉包

func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

print(exec{_,_ in 100 })  // 100
複製代碼

輸出

100

自動閉包

函數

假設咱們定義一個這樣的函數,要求 若是第1個數大於0,返回第一個數。不然返回第2個數

func getFirstPositive(_ v1: Int, _ v2:  Int) -> Int? {
    return v1 > 0 ? v1 : v2
    
}
    
//調用
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4) // -4
複製代碼

如今假如說,咱們這麼傳入的話

func getNum() -> Int {
	// 這裏每次都執行
    let a = 100
    let b = 200
    return a + b
}

func getFirstPositive2(_ v1: Int, _ v2:  Int) -> Int? {
    return v1 > 0 ? v1 : v2
    
}

getFirstPositive2(10, getNum())

複製代碼

改爲函數類型的參數

由於第一個參數已是10 大於0了,第二個參數,也就是getNum() 根本不必去執行,浪費性能,因此,有沒有什麼辦法能作到,當第一個參數不知足時候,纔去執行getNum()呢?答案是有的

// 改爲函數類型的參數,可讓v2延遲加載
func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
	// 這裏判斷 v1 > 0 不會調用 v2()
    return v1 > 0 ? v1 : v2()
}

getFirstPositive2(10, {
	// 第一個參數大於0的時候,這裏不會執行
    let a = 100
    let b = 200
    return a + b
})

複製代碼

改進

若是改爲這樣寫就報錯了

/ 改爲函數類型的參數,可讓v2延遲加載
func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
	// 這裏判斷 v1 > 0 不會調用 v2()
    return v1 > 0 ? v1 : v2()
}

getFirstPositive2(10,  20) //報錯 Cannot convert value of type 'Int' to expected argument type '() -> Int'
複製代碼

由於須要的是() -> Int類型,給的是Int

咱們能夠寫成下面兩種均可以

/ 改爲函數類型的參數,可讓v2延遲加載
func getFirstPositive2(_ v1: Int, _ v2: () -> Int) -> Int? {
	// 這裏判斷 v1 > 0 不會調用 v2()
    return v1 > 0 ? v1 : v2()
}

getFirstPositive2(10) { 20}

getFirstPositive2(10, {20})
複製代碼

@autoclosure

上面的也能夠用自動閉包技術

func getFirstPositive3(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
    return v1 > 0 ? v1 : v2()
}
getFirstPositive3(-4, 20)
複製代碼

須要的注意點:

  • 爲了不與指望衝突,使用了@autoclosure的地方最好明確註釋清楚:這個值會被推遲執行
  • @autoclosure 會自動將 20 封裝成閉包 { 20 }
  • @autoclosure 只支持 () -> T 格式的參數 n@autoclosure 並不是只支持最後1個參數
  • 空合併運算符 ?? 使用了 @autoclosure 技術
  • 有@autoclosure、無@autoclosure,構成了函數重載

Swift官方源碼

從入門到精通Swift編程

更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。

相關文章
相關標籤/搜索