Swift語(yǔ)法 Swift5 【07 - 閉包】


  • 作者: Liwx
  • 郵箱: 1032282633@qq.com
  • 源碼: 需要源碼的同學(xué), 可以在評(píng)論區(qū)留下您的郵箱

iOS Swift 語(yǔ)法 底層原理內(nèi)存管理分析 專(zhuān)題:【iOS Swift5語(yǔ)法】

00 - 匯編
01 - 基礎(chǔ)語(yǔ)法
02 - 流程控制
03 - 函數(shù)
04 - 枚舉
05 - 可選項(xiàng)
06 - 結(jié)構(gòu)體和類(lèi)
07 - 閉包
08 - 屬性
09 - 方法
10 - 下標(biāo)
11 - 繼承
12 - 初始化器init
13 - 可選項(xiàng)


目錄

  • 01-閉包表達(dá)式
  • 02-閉包表達(dá)式的簡(jiǎn)寫(xiě)
  • 03-尾隨閉包
  • 04-示例-數(shù)組的排序
  • 05-忽略參數(shù)
  • 06-閉包(Closure)
  • 07-注意
  • 08-自動(dòng)閉包

01-閉包表達(dá)式

  • Swift通過(guò)func定義一個(gè)函數(shù),也可以通過(guò)閉包表達(dá)式定義一個(gè)函數(shù)

  • 閉包表達(dá)式格式

{
    (參數(shù)列表) -> 返回值類(lèi)型 in
    函數(shù)體代碼
}

  • 使用閉包調(diào)用時(shí), 不需要寫(xiě)參數(shù)標(biāo)簽
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }

var fn = {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}
// 閉包調(diào)用不需要寫(xiě)參數(shù)標(biāo)簽
fn(10, 20)
  • 類(lèi)似匿名函數(shù)
{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(10, 20)

02-閉包表達(dá)式的簡(jiǎn)寫(xiě)

func exec(v1: Int, v2: Int , fn:(Int, Int) -> Int) {
    print(fn(v1, v2))
}

// 完整寫(xiě)法
exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in
    return v1 + v2
})

// 簡(jiǎn)寫(xiě)1 省略參數(shù)類(lèi)型
exec(v1: 10, v2: 20, fn: { (v1, v2) -> Int in
    return v1 + v2
})

// 簡(jiǎn)寫(xiě)2 省略 返回值, 參數(shù)類(lèi)型, 小括號(hào). 如果閉包體只有一個(gè)表達(dá)式, 省略return
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2
})

// 簡(jiǎn)寫(xiě)3 省略參數(shù), 使用$0 $1表示參數(shù)
exec(v1: 10, v2: 20, fn: {
    $0 + $1
})

// 簡(jiǎn)寫(xiě)4 閉包參數(shù)直接寫(xiě)運(yùn)算符
exec(v1: 10, v2: 20, fn: +)

// Xcode 自動(dòng)生成 尾隨閉包
exec(v1: 10, v2: 20) { (v1, v2) -> Int in
    v1 + v2
}

03-尾隨閉包

  • 如果將一個(gè)很長(zhǎng)的閉包表達(dá)式作為函數(shù)的最后一個(gè)實(shí)參, 使用尾隨閉包可以增強(qiáng)函數(shù)的可讀性
    • 尾隨閉包是一個(gè)被書(shū)寫(xiě)在函數(shù)調(diào)用括號(hào)外面(后面)的閉包表達(dá)式

  • 尾隨閉包簡(jiǎn)單使用
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

// 尾隨閉包
exec(v1: 10, v2: 20) {
    $0 + $1
}

  • 如果閉包表達(dá)式是函數(shù)的唯一實(shí)參,而且使用了尾隨閉包的語(yǔ)法,那就不需要在函數(shù)名后面寫(xiě)圓括號(hào)
func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

// 簡(jiǎn)寫(xiě)
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }

// Xcode 自動(dòng)生成
exec { (v1, v2) -> Int in
    v1 + v2
}

04-示例-數(shù)組的排序

  • 數(shù)組排序
var arr = [10, 1, 4, 20, 99]
// 從小到大
arr.sort()  // [1, 4, 10, 20, 99]
// 自定義排序
// 從大到小
arr.sort { (v1, v2) -> Bool in
    v1 > v2
}
print(arr)  // [99, 20, 10, 4, 1]
  • Swift標(biāo)準(zhǔn)庫(kù)sort函數(shù)
@inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows

  • 函數(shù)當(dāng)做閉包調(diào)用
/// 數(shù)值比較
/// - Parameters:
///   - i1: 數(shù)值1
///   - i2: 數(shù)值2
/// - Returns: true: i1排在i2前面, false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {
    return i1 > i2
}

var nums = [11, 2, 18, 6, 5, 68, 45]
nums.sort(by: cmp)
print(nums) // [68, 45, 18, 11, 6, 5, 2]
  • sort函數(shù)從小到大排序幾種寫(xiě)法
nums.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})
nums.sort { (i1, i2) -> Bool in
    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 }

print(nums) // [2, 5, 6, 11, 18, 45, 68]

05-忽略參數(shù)

  • 使用下劃線(xiàn)_忽略參數(shù)
func exec(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

exec { $0 + $1 }

//var fn = { $0 + $1 }    // 編譯器無(wú)法推斷出類(lèi)型 error: ambiguous use of operator '+'
var fn: (Int, Int) -> Int = { $0 + $1 }
exec(fn: fn)

// 忽略參數(shù)
exec {_, _ in 10}   // 10

06-閉包(Closure)

  • 關(guān)于閉包的定義
    • 一個(gè)函數(shù)和他所捕獲的變量/常量環(huán)境組合起來(lái),稱(chēng)為閉包
      • 一般指定義在函數(shù)內(nèi)部函數(shù)
      • 一般它捕獲的是外層函數(shù)局部變量/常量

func fn() -> () -> () {
    var a = 10
    
    func fn1() {
        // 捕獲的是外層函數(shù)的局部變量/常量
        a = 10
    }
    return fn1
}

  • 匯編分析函數(shù)內(nèi)存布局
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn3 = sum // 函數(shù)地址占用16字節(jié),前8個(gè)字節(jié)存放函數(shù)地址, 后8個(gè)字節(jié)存放0
print(fn3(10, 20))
 // rcx存放函數(shù)地址: 0x100002524 + 0x146c = 0x100003990
 0x10000251d <+6125>: leaq   0x146c(%rip), %rcx        ; _7_閉包.sum(Swift.Int, Swift.Int) -> Swift.Int at main.swift:292
 0x100002524 <+6132>: movq   %rcx, 0x2f15(%rip)        ; _7_閉包.fn3 : (Swift.Int, Swift.Int) -> Swift.Int
 0x10000252b <+6139>: movq   $0x0, 0x2f12(%rip)        ; _7_閉包.fn3 : (Swift.Int, Swift.Int) -> Swift.Int + 4

  • 匯編分析swift_allocObject(n)函數(shù)
    • swift_allocObject(n) // 至少需要n個(gè)字節(jié),實(shí)際返回不一定是n個(gè)字節(jié)
    • lldb: bt命令打印函數(shù)調(diào)用棧
class Test {
    // 8, 8
    var test1: Int = 0  // 8
    var test2: Int = 0  // 8
}
var test = Test()   // 斷點(diǎn)調(diào)試, 內(nèi)部會(huì)調(diào)用__allocating_init
 07-閉包`Test.__allocating_init():
 ->  0x1000038d0 <+0>:  pushq  %rbp
     0x1000038d1 <+1>:  movq   %rsp, %rbp
     0x1000038d4 <+4>:  pushq  %r13
     0x1000038d6 <+6>:  pushq  %rax
     0x1000038d7 <+7>:  movl   $0x20, %esi              ; 分配32個(gè)字節(jié)
     0x1000038dc <+12>: movl   $0x7, %edx
     0x1000038e1 <+17>: movq   %r13, %rdi
     0x1000038e4 <+20>: callq  0x100003c1e               ; symbol stub for: swift_allocObject
     0x1000038e9 <+25>: movq   %rax, %r13
     0x1000038ec <+28>: callq  0x100003900               ; _7_閉包.Test.init() -> _7_閉包.Test at main.swift:94
     0x1000038f1 <+33>: addq   $0x8, %rsp
     0x1000038f5 <+37>: popq   %r13
     0x1000038f7 <+39>: popq   %rbp
     0x1000038f8 <+40>: retq

  • 使用bt指令查看函數(shù)調(diào)用棧
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
  * frame #0: 0x00007fff68bf6ce0 libsystem_malloc.dylib`malloc
    frame #1: 0x00007fff6835cca9 libswiftCore.dylib`swift_slowAlloc + 25
    frame #2: 0x00007fff6835cd27 libswiftCore.dylib`swift_allocObject + 39
    frame #3: 0x0000000100001a5d 07-閉包`main at main.swift:203:36
    frame #4: 0x00007fff68a40cc9 libdyld.dylib`start + 1

  • 匯編窺探閉包的本質(zhì)

    • 內(nèi)層函數(shù)沒(méi)有捕獲外層函數(shù)的局部變量/常量
typealias Fn = (Int) -> Int
 func getFn() -> Fn {
     // 局部變量
     var num = 0
     func plus(_ i: Int) -> Int {
         return i
     }
     
     return plus  
 }
  
 var fn = getFn()   // 返回的是函數(shù)地址,16字節(jié), 低8字節(jié): plus函數(shù)地址,高8字節(jié):0
 fn(1)   // 1
 fn(2)   // 3
 fn(3)   // 6
 fn(4)   // 10
image.png
image.png
image.png

  • 內(nèi)層函數(shù)有捕獲外層函數(shù)的局部變量/常量

    • 閉包存儲(chǔ)和類(lèi)類(lèi)似: 前8字節(jié): 閉包信息
    • 如果捕獲了外層函數(shù)的局部變量/常量, 則底層會(huì)調(diào)用: swift_allocObject 函數(shù), 分配堆空間來(lái)存儲(chǔ)需要捕獲的變量/常量
    • 函數(shù)指針 地址16個(gè)字節(jié),前8個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址; 有捕獲參數(shù): num 后8個(gè)字節(jié)為 堆空間的地址, 沒(méi)捕獲: 后8個(gè)字節(jié)為 0
typealias Fn = (Int) -> Int
func getFn() -> Fn {
    // 局部變量
    var num = 0     // num占用24字節(jié), 8: 類(lèi)型 8: 引用計(jì)數(shù) 8: num值
    
    // 如果plus捕獲了局部變量/常量, 可以認(rèn)為plus函數(shù)傳遞了兩個(gè)參數(shù),i, 和num堆空間的地址值(用于plus內(nèi)部訪(fǎng)問(wèn)堆空間num值)
    func plus(_ i: Int) -> Int {
        // 捕獲的是外層函數(shù)的局部變量/常量
        num += i
        return num  // 斷點(diǎn)調(diào)試, 觀(guān)察num值變化
    }
    
    // 如果有捕獲的是外層函數(shù)的局部變量/常量, 在return之前會(huì)將局部變量num數(shù)據(jù)保存到分配的堆空間中
    return plus     // 斷點(diǎn)調(diào)試, 目的是要在swift_allocObject執(zhí)行后返回分配的堆空間地址, 之后一行語(yǔ)句打斷點(diǎn)
}   // 返回的plus和num形成了閉包

// 函數(shù)指針fn 地址16個(gè)字節(jié),前8個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址, 有捕獲參數(shù): num 后8個(gè)字節(jié): 堆空間的地址, 沒(méi)捕獲: 后8個(gè)字節(jié): 0
var fn = getFn() // 返回的是函數(shù)地址,16字節(jié), 低8字節(jié): 間接調(diào)用plus函數(shù)地址,高8字節(jié):閉包地址
print(MemoryLayout.stride(ofValue: fn1))    // 16

fn(1)   // 1   0x0000000100004078 0x0000000000000002 0x0000000000000001 0x0000000000000000
fn(2)   // 3   0x0000000100004078 0x0000000000000002 0x0000000000000003 0x0000000000000000
fn(3)   // 6   0x0000000100004078 0x0000000000000002 0x0000000000000006 0x0000000000000000
fn(4)   // 10  0x0000000100004078 0x0000000000000002 0x000000000000000a 0x0000000000000000
image.png
image.png
  • 閉包捕獲一個(gè)參數(shù)的內(nèi)存布局
    D8FB8159-0DC8-47B0-9B55-32D5293FB403.png

  • 可以把閉包想象成是一個(gè)類(lèi)的實(shí)例對(duì)象
    • 內(nèi)存在堆空間
    • 捕獲的局部變量/常量就是對(duì)象的成員(存儲(chǔ)屬性)
    • 組成閉包的函數(shù)就是類(lèi)內(nèi)部定義的方法
// 閉包類(lèi)似于以下類(lèi)實(shí)例對(duì)象
class Closure {
    var num = 0
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
}

var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1) // 1
cs2.plus(2) // 2
cs1.plus(3) // 4
cs2.plus(4) // 6
cs1.plus(5) // 9
cs2.plus(6) // 12
  • 窺探閉包捕獲多個(gè)參數(shù)

typealias Fn = (Int) -> Int
func getFn() -> Fn {
    // 局部變量
    var num = 0     // num占用24字節(jié), 8: 類(lèi)型 8: 引用計(jì)數(shù) 8: num值
    var count = 0
    
    // 如果plus捕獲了局部變量/常量, 可以認(rèn)為plus函數(shù)傳遞了兩個(gè)參數(shù),i, 和num堆空間的地址值(用于plus內(nèi)部訪(fǎng)問(wèn)堆空間num值)
    func plus(_ i: Int) -> Int {
        // 捕獲的是外層函數(shù)的局部變量/常量
        num += i
        count += 1
        return num  // 斷點(diǎn)調(diào)試, 觀(guān)察num值變化
    }
    
    // 如果有捕獲的是外層函數(shù)的局部變量/常量, 在return之前會(huì)將局部變量num數(shù)據(jù)保存到分配的堆空間中
    return plus     // 斷點(diǎn)調(diào)試, 目的是要在swift_allocObject執(zhí)行后返回分配的堆空間地址, 之后一行語(yǔ)句打斷點(diǎn)
}   // 返回的plus和num形成了閉包

// 閉包地址16位,前8個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址, 有捕獲參數(shù): num 后8個(gè)字節(jié): 堆空間的地址, 沒(méi)捕獲: 后8個(gè)字節(jié): 0

var fn = getFn()
fn(1)   // 1
fn(2)   // 3
fn(3)   // 6
fn(4)   // 10
image.png
0E79C50B-E1AF-4356-AF15-DDA93AB96608.png
  • 閉包捕獲多個(gè)參數(shù)的內(nèi)存布局
    EBF109E8-74CC-4833-A9F1-576426C6E48F.png

07-注意

  • 如果返回值是函數(shù)類(lèi)型, 那么參數(shù)的修飾要保持統(tǒng)一
// 返回值是函數(shù)類(lèi)型為(inout Int) -> Void
func add(_ num: Int) -> (inout Int) -> Void {
    func plus(v: inout Int) {  // 參數(shù)也必須修飾為inout Int類(lèi)型 
        v += num
    }
    return plus
}

var num = 5
add(20)(&num)  // plus參數(shù)類(lèi)型是inout Int, 調(diào)用是需要加上&符號(hào)
print(num)  // 25

08-自動(dòng)閉包

  • 非自動(dòng)閉包
// 如果第1個(gè)數(shù)大于0,返回第1個(gè)數(shù),否則返回第2個(gè)數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20)    // 10
getFirstPositive(-2, 20)    // 20
getFirstPositive(0, -4)     // -4
// 改成函數(shù)類(lèi)型的參數(shù),可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
    print("getFirstPositive1")
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, {20})  // 20
getFirstPositive(-4) {20}   // 20
  • 自動(dòng)閉包的使用
    • @autoclosure 會(huì)自動(dòng)將n封裝成閉包{ n }
    • @autoclosure 只支持() -> T格式的參數(shù)
    • @autoclosure 并非只支持最后一個(gè)參數(shù)
    • 空合并運(yùn)算符?? 使用了@autoclosure技術(shù)
    • 有@autoclosure,無(wú)@autoclosure,構(gòu)成了函數(shù)重載
    • 為了避免與期望沖突,使用了@autoclosure的地方最好明確注釋清楚: 這個(gè)值會(huì)被推遲執(zhí)行
// 非自動(dòng)閉包
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
    print("getFirstPositive1")
    return v1 > 0 ? v1 : v2()
}
// 自動(dòng)閉包寫(xiě)法  函數(shù)重載
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
    print("getFirstPositive2")
    return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20)    // 無(wú)需加大括號(hào),@autoclosure 會(huì)自動(dòng)將20封裝成閉包{ 20 }
getFirstPositive(-4, {30})  // 調(diào)用getFirstPositive(_ v1: Int, _ v2: () -> Int),  30

iOS Swift 語(yǔ)法 底層原理內(nèi)存管理分析 專(zhuān)題:【iOS Swift5語(yǔ)法】

下一篇: 08 - 屬性
上一篇: 06 - 結(jié)構(gòu)體和類(lèi)


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。