闭包
什么是闭包
1.官方定义
- 闭包是可以在代码中被传递和引用的功能型独立模块。类似于OC中的block以及其他语言的匿名函数
- 闭包能够捕捉和存储定义在其上下文中的任何常量和变量的引用
- 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
证明过程:
-
先将funcPlus函数中使用num的代码注释 打断点查看汇编,即可看到 return返回的是funcPlus函数的地址( leaq 0xd(%rip), %rax )
rax存放的是funcPlus的函数的地址 -
然后将函数还原,再调试,通过汇编查看 getFn()复杂了很多 同时发现了一个非常重要的信息 调用了swift_allocObject,也就意味着分配了一段堆空间的内存,用来存放num
当内部函数访问了外部函数的局部变量,就会分配堆空间捕获这个变量,叫捕获 即可延长该变量的生命周期
捕获
- 闭包能够从上下文捕获已被定义的常量和变量。
- 即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值
继续上面的例子,如果证明分配的这一块堆空间存放的是num呢?
在malloc下面这一条打断点 register read rax 得到堆空间的地址
然后再将函数Funcplus 中return该行打断点 执行下一步 即fn(1)调用完毕后查看堆空间的值 即可看到该地址前16个字节 和类的实例对象分配的堆空间一样(引用类型+引用计数)接下来8个字节即为num 为 1的值,继续下一步 num的值为3 该地址中也确实为3 即证明
补充:
-
在最初swiftallocObject调用完之后,访问分配的堆空间,里面为垃圾数据
image -
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: < )
作业:
-
上面的例子 如果num为全局变量呢?
不符合闭包的定义,捕获的是外部函数的局部变量
(2)题目2,分配的堆空间的大小是多少?
image
网友评论