美文网首页Swift
Swift语法 Swift5 【07 - 闭包】

Swift语法 Swift5 【07 - 闭包】

作者: Liwx | 来源:发表于2020-05-08 17:18 被阅读0次

    • 作者: Liwx
    • 邮箱: 1032282633@qq.com
    • 源码: 需要源码的同学, 可以在评论区留下您的邮箱

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    00 - 汇编
    01 - 基础语法
    02 - 流程控制
    03 - 函数
    04 - 枚举
    05 - 可选项
    06 - 结构体和类
    07 - 闭包
    08 - 属性
    09 - 方法
    10 - 下标
    11 - 继承
    12 - 初始化器init
    13 - 可选项


    目录

    • 01-闭包表达式
    • 02-闭包表达式的简写
    • 03-尾随闭包
    • 04-示例-数组的排序
    • 05-忽略参数
    • 06-闭包(Closure)
    • 07-注意
    • 08-自动闭包

    01-闭包表达式

    • Swift通过func定义一个函数,也可以通过闭包表达式定义一个函数

    • 闭包表达式格式

    {
        (参数列表) -> 返回值类型 in
        函数体代码
    }
    

    • 使用闭包调用时, 不需要参数标签
    func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
    
    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)
    

    02-闭包表达式的简写

    func exec(v1: Int, v2: Int , fn:(Int, Int) -> Int) {
        print(fn(v1, v2))
    }
    
    // 完整写法
    exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in
        return v1 + v2
    })
    
    // 简写1 省略参数类型
    exec(v1: 10, v2: 20, fn: { (v1, v2) -> Int in
        return v1 + v2
    })
    
    // 简写2 省略 返回值, 参数类型, 小括号. 如果闭包体只有一个表达式, 省略return
    exec(v1: 10, v2: 20, fn: {
        v1, v2 in v1 + v2
    })
    
    // 简写3 省略参数, 使用$0 $1表示参数
    exec(v1: 10, v2: 20, fn: {
        $0 + $1
    })
    
    // 简写4 闭包参数直接写运算符
    exec(v1: 10, v2: 20, fn: +)
    
    // Xcode 自动生成 尾随闭包
    exec(v1: 10, v2: 20) { (v1, v2) -> Int in
        v1 + v2
    }
    

    03-尾随闭包

    • 如果将一个很长的闭包表达式作为函数的最后一个实参, 使用尾随闭包可以增强函数的可读性
      • 尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式

    • 尾随闭包简单使用
    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 }
    
    // Xcode 自动生成
    exec { (v1, v2) -> Int in
        v1 + v2
    }
    

    04-示例-数组的排序

    • 数组排序
    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标准库sort函数
    @inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
    

    • 函数当做闭包调用
    /// 数值比较
    /// - Parameters:
    ///   - i1: 数值1
    ///   - i2: 数值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函数从小到大排序几种写法
    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-忽略参数

    • 使用下划线_忽略参数
    func exec(fn: (Int, Int) -> Int) {
        print(fn(1, 2))
    }
    
    exec { $0 + $1 }
    
    //var fn = { $0 + $1 }    // 编译器无法推断出类型 error: ambiguous use of operator '+'
    var fn: (Int, Int) -> Int = { $0 + $1 }
    exec(fn: fn)
    
    // 忽略参数
    exec {_, _ in 10}   // 10
    

    06-闭包(Closure)

    • 关于闭包的定义
      • 一个函数和他所捕获的变量/常量环境组合起来,称为闭包
        • 一般指定义在函数内部函数
        • 一般它捕获的是外层函数局部变量/常量

    func fn() -> () -> () {
        var a = 10
        
        func fn1() {
            // 捕获的是外层函数的局部变量/常量
            a = 10
        }
        return fn1
    }
    

    • 汇编分析函数内存布局
    func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
    var fn3 = sum // 函数地址占用16字节,前8个字节存放函数地址, 后8个字节存放0
    print(fn3(10, 20))
    
     // rcx存放函数地址: 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)函数
      • swift_allocObject(n) // 至少需要n个字节,实际返回不一定是n个字节
      • lldb: bt命令打印函数调用栈
    class Test {
        // 8, 8
        var test1: Int = 0  // 8
        var test2: Int = 0  // 8
    }
    var test = Test()   // 断点调试, 内部会调用__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个字节
         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指令查看函数调用栈
    (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
    

    • 汇编窥探闭包的本质

      • 内层函数没有捕获外层函数的局部变量/常量
    typealias Fn = (Int) -> Int
     func getFn() -> Fn {
         // 局部变量
         var num = 0
         func plus(_ i: Int) -> Int {
             return i
         }
         
         return plus  
     }
      
     var fn = getFn()   // 返回的是函数地址,16字节, 低8字节: plus函数地址,高8字节:0
     fn(1)   // 1
     fn(2)   // 3
     fn(3)   // 6
     fn(4)   // 10
    
    image.png image.png image.png
    • 内层函数有捕获外层函数的局部变量/常量

      • 闭包存储和类类似: 前8字节: 闭包信息
      • 如果捕获了外层函数的局部变量/常量, 则底层会调用: swift_allocObject 函数, 分配堆空间来存储需要捕获的变量/常量
      • 函数指针 地址16个字节,前8个字节: 不是plus函数地址,是间接调用plus的函数地址; 有捕获参数: num 后8个字节为 堆空间的地址, 没捕获: 后8个字节为 0
    typealias Fn = (Int) -> Int
    func getFn() -> Fn {
        // 局部变量
        var num = 0     // num占用24字节, 8: 类型 8: 引用计数 8: num值
        
        // 如果plus捕获了局部变量/常量, 可以认为plus函数传递了两个参数,i, 和num堆空间的地址值(用于plus内部访问堆空间num值)
        func plus(_ i: Int) -> Int {
            // 捕获的是外层函数的局部变量/常量
            num += i
            return num  // 断点调试, 观察num值变化
        }
        
        // 如果有捕获的是外层函数的局部变量/常量, 在return之前会将局部变量num数据保存到分配的堆空间中
        return plus     // 断点调试, 目的是要在swift_allocObject执行后返回分配的堆空间地址, 之后一行语句打断点
    }   // 返回的plus和num形成了闭包
    
    // 函数指针fn 地址16个字节,前8个字节: 不是plus函数地址,是间接调用plus的函数地址, 有捕获参数: num 后8个字节: 堆空间的地址, 没捕获: 后8个字节: 0
    var fn = getFn() // 返回的是函数地址,16字节, 低8字节: 间接调用plus函数地址,高8字节:闭包地址
    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
    • 闭包捕获一个参数的内存布局
      D8FB8159-0DC8-47B0-9B55-32D5293FB403.png

    • 可以把闭包想象成是一个类的实例对象
      • 内存在堆空间
      • 捕获的局部变量/常量就是对象的成员(存储属性)
      • 组成闭包的函数就是类内部定义的方法
    // 闭包类似于以下类实例对象
    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
    
    • 窥探闭包捕获多个参数
    
    typealias Fn = (Int) -> Int
    func getFn() -> Fn {
        // 局部变量
        var num = 0     // num占用24字节, 8: 类型 8: 引用计数 8: num值
        var count = 0
        
        // 如果plus捕获了局部变量/常量, 可以认为plus函数传递了两个参数,i, 和num堆空间的地址值(用于plus内部访问堆空间num值)
        func plus(_ i: Int) -> Int {
            // 捕获的是外层函数的局部变量/常量
            num += i
            count += 1
            return num  // 断点调试, 观察num值变化
        }
        
        // 如果有捕获的是外层函数的局部变量/常量, 在return之前会将局部变量num数据保存到分配的堆空间中
        return plus     // 断点调试, 目的是要在swift_allocObject执行后返回分配的堆空间地址, 之后一行语句打断点
    }   // 返回的plus和num形成了闭包
    
    // 闭包地址16位,前8个字节: 不是plus函数地址,是间接调用plus的函数地址, 有捕获参数: num 后8个字节: 堆空间的地址, 没捕获: 后8个字节: 0
    
    var fn = getFn()
    fn(1)   // 1
    fn(2)   // 3
    fn(3)   // 6
    fn(4)   // 10
    
    image.png 0E79C50B-E1AF-4356-AF15-DDA93AB96608.png
    • 闭包捕获多个参数的内存布局
      EBF109E8-74CC-4833-A9F1-576426C6E48F.png

    07-注意

    • 如果返回值是函数类型, 那么参数的修饰要保持统一
    // 返回值是函数类型为(inout Int) -> Void
    func add(_ num: Int) -> (inout Int) -> Void {
        func plus(v: inout Int) {  // 参数也必须修饰为inout Int类型 
            v += num
        }
        return plus
    }
    
    var num = 5
    add(20)(&num)  // plus参数类型是inout Int, 调用是需要加上&符号
    print(num)  // 25
    

    08-自动闭包

    • 非自动闭包
    // 如果第1个数大于0,返回第1个数,否则返回第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
    
    // 改成函数类型的参数,可以让v2延迟加载
    func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
        print("getFirstPositive1")
        return v1 > 0 ? v1 : v2()
    }
    getFirstPositive(-4, {20})  // 20
    getFirstPositive(-4) {20}   // 20
    
    • 自动闭包的使用
      • @autoclosure 会自动将n封装成闭包{ n }
      • @autoclosure 只支持() -> T格式的参数
      • @autoclosure 并非只支持最后一个参数
      • 空合并运算符?? 使用了@autoclosure技术
      • 有@autoclosure,无@autoclosure,构成了函数重载
      • 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚: 这个值会被推迟执行
    // 非自动闭包
    func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
        print("getFirstPositive1")
        return v1 > 0 ? v1 : v2()
    }
    // 自动闭包写法  函数重载
    func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
        print("getFirstPositive2")
        return v1 > 0 ? v1 : v2()
    }
    getFirstPositive(-4, 20)    // 无需加大括号,@autoclosure 会自动将20封装成闭包{ 20 }
    getFirstPositive(-4, {30})  // 调用getFirstPositive(_ v1: Int, _ v2: () -> Int),  30
    

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    下一篇: 08 - 属性
    上一篇: 06 - 结构体和类


    相关文章

      网友评论

        本文标题:Swift语法 Swift5 【07 - 闭包】

        本文链接:https://www.haomeiwen.com/subject/ayxqnhtx.html