九、闭包 闭包表达式

闭包

什么是闭包

1.官方定义

  • 闭包是可以在代码中被传递引用功能型独立模块。类似于OC中的block以及其他语言的匿名函数
  • 闭包能够捕捉存储定义在其上下文中的任何常量和变量的引用
  1. MJ认为严谨的定义
  • 一个函数和它所捕获的变量/常量环境组合起来,称为闭包,其中:
    • 函数是定义在外部函数内部的内嵌函数
    • 捕获的是外层函数的局部变量/常量
闭包的本质
  • 闭包是引用类型,可以把闭包想象成一个类的实例对象(和类进行对比,非常相似)
  • 内存在堆空间
  • 捕获的局部变量/常量就是对象的成员(存储属性)
  • 组成闭包的函数就是类内部定义的方法

如何证明呢?汇编!

//为什么呢?num为getFn()函数内定义的局部变量 为何num的值能够保留?
typealias Fn = (Int) -> Int

func getFn() -> Fn {
    var num = 0
    func functionPlus(_ v1:Int) ->Int{
        num += v1
        return num
    }
    return functionPlus(_:)
    
}

var fn = getFn()

print(fn(1))//输出:1
print(fn(2))//输出:3
print(fn(3))//输出:6
print(fn(4))//输出:10

证明过程

  1. 先将funcPlus函数中使用num的代码注释 打断点查看汇编,即可看到 return返回的是funcPlus函数的地址( leaq 0xd(%rip), %rax )


    rax存放的是funcPlus的函数的地址
  2. 然后将函数还原,再调试,通过汇编查看 getFn()复杂了很多 同时发现了一个非常重要的信息 调用了swift_allocObject,也就意味着分配了一段堆空间的内存,用来存放num


    当内部函数访问了外部函数的局部变量,就会分配堆空间捕获这个变量,叫捕获 即可延长该变量的生命周期
捕获
  • 闭包能够从上下文捕获已被定义的常量和变量。
  • 即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值

继续上面的例子,如果证明分配的这一块堆空间存放的是num呢?
在malloc下面这一条打断点 register read rax 得到堆空间的地址


然后再将函数Funcplus 中return该行打断点 执行下一步 即fn(1)调用完毕后查看堆空间的值
即可看到该地址前16个字节 和类的实例对象分配的堆空间一样(引用类型+引用计数)接下来8个字节即为num 为 1的值,继续下一步
num的值为3 该地址中也确实为3 即证明

补充:

  1. 在最初swiftallocObject调用完之后,访问分配的堆空间,里面为垃圾数据


    image
  2. swiftallocObject分配了多少字节?


    申请24个字节,实际分配的是32个字节(因为在堆空间是以16个字节为一组)

闭包表达式

闭包表达式语法(区分开闭包和闭包表达式 完全不同的两个东西)
闭包表达式语法的一般形式
通过闭包表达式来定义函数
//通过闭包表达式来定义函数
var fn = {
    (v1:Int, v2:Int) -> Int in
    v1 + v2
}

fn(10,20)
闭包表达式的简写
//Exec函数
func Exec(v1:Int, v2:Int, fn:(Int, Int) -> Int) {
    print(fn(v1,v2))
}

func Sum(_ v1 :Int, _ v2 :Int ) -> Int {
    v1 + v2
}

//Sum函数作为Exec(v1:v2:fn:)的形参传入
Exec(v1: 10, v2: 20, fn: Sum(_:_:))

//闭包表达式 单表达式隐式返回
Exec(v1: 10, v2: 20, fn: {(v1:Int, v2:Int) -> Int in
    v1 + v2
})

//尾随闭包  后面具体讲述
Exec(v1: 10, v2: 20){(v1:Int, v2:Int) -> Int in
    v1 + v2
}

//从语境推断类型
//因为形参类型以及返回值类型能通过语境中确定 可以省略()、->、形参、返回值类型
Exec(v1: 10, v2: 20, fn: {v1, v2 in v1 + v2})

//尾随闭包
Exec(v1: 10, v2: 20){
    v1, v2 in v1 + v2
}

//简写实际参数名
Exec(v1: 10, v2: 20, fn: {$0 + $1})

//尾随闭包
Exec(v1: 10, v2: 20){
    $0 + $1
}

//运算符函数
Exec(v1: 10, v2: 20, fn: +)

Exec(v1: 10, v2: 20){ + } //报错 不能这么写 此"+"被判定为一元运算符正号 原因未知
尾随闭包
  • 将很长的闭包表达式作为函数最后一个实参时,使用尾随闭包,增强函数可读性
  • 尾随闭包是一个被写在函数调用括号后面的闭包表达式 (见上栗)
  • 闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要再函数后面写圆括号
//Exec函数
func Exec(fn:(Int, Int) -> Int) {
    print(fn(11,2))
}

Exec(fn: {$0 + $1})
Exec(){ $0 + $1 }
Exec{ $0 + $1 }

示例: 数组排序sorted:

var name = ["abc", "adc", "abd", "bac"]

func backward(_ s1:String, _ s2:String) -> Bool {
    return s1 < s2
}

//函数作为sorted(by:)
var reveredNames = name.sorted(by: backward(_:_:))
//闭包表达式 隐式返回
var revered2Names = name.sorted(by: {(s1 :String, s2 :String) -> Bool in
    return s1 < s2
})

//var revered2Names = name.sorted { (s1 :String, s2 :String) -> Bool in
//    return s1 < s2
//}//尾随闭包
//从语境推断类型
//因为形参类型以及返回值类型能通过语境中确定 可以省略()、->、形参、返回值类型
var revered3Names = name.sorted(by: { s1, s2 -> Bool in
    s1 < s2
})

//var revered3Names = name.sorted { s1, s2 -> Bool in
//    s1 < s2
//}//尾随闭包
//简写实际参数名
var revered4Names = name.sorted(by: { $0 < $1})
//var revered4Names = name.sorted { $0 < $1}//尾随闭包
//运算符函数
var revered5Names = name.sorted(by: < )

作业:

  1. 上面的例子 如果num为全局变量呢?


    不符合闭包的定义,捕获的是外部函数的局部变量

(2)题目2,分配的堆空间的大小是多少?

image

你可能感兴趣的:(九、闭包 闭包表达式)