闭包表达式(Closure Expresssion)
在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数
/// 方法
func sum(_ v1: Int, _v2: Int) -> Int {
v1 + v2
}
/// 闭包
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
闭包结构:
{
(参数列表) -> 返回值类型 in
函数体代码
}
闭包表达式的简写
/// 定义一个函数,有三个参数:v1、v2、一个函数
func test(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
/// 将函数用闭包代替
test(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) in
return v1 + v2
})
/// 简写闭包
test(v1: 10, v2: 20, fn: { v1, v2 in
return v1 + v2
})
/// 去掉return
test(v1: 10, v2: 20, fn: { v1, v2 in
v1 + v2
})
/// 用美元符代替 $0、$1
test(v1: 10, v2: 20, fn: {$0 + $1})
/// 直接写一个加号
test(v1: 10, v2: 20, fn: +)
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
尾随闭包就是被书写在函数调用括号外面(后面)的闭包表达式
/// 函数
func test(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
/// 调用
test(v1: 10, v2: 20) {
$0 + $1
}
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
func test(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
/// 调用
test {
$0 + $1
}
示例 - 数组的排序
@inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
var arr = [10, 1, 2, 8]
// 正常闭包表达式
arr.sort(by: {v1, v2 in
return v1 > v2
})
// 尾随闭包
arr.sort { v1, v2 in
return v1 > v2
}
// 省略return
arr.sort { v1, v2 in
v1 > v2
}
// 用美元符代替
arr.sort {
$0 > $1
}
// 直接用大于号代替
arr.sort(by: >)
闭包(Closure)
一个函数和它捕获的变量/常量环境组合起来,称为闭包
一般定义在函数内部的函数
一般它捕获的外层函数的局部变量/常量
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
}
var fn = getFn()
print(fn(1)) // 1
print(fn(2)) // 3
print(fn(3)) // 6
print(fn(4)) // 10
按道理说num是局部变量,在函数执行结束时就会被销毁,但是多次调用fn,num的值却是累加的,是因为getFn函数内部的plus函数和局部变量num形成了闭包,捕获了局部变量num,num被保存在了堆空间,所以调用fn时num没有被销毁,可以通过汇编代码观察,调用了alloc方法,开辟了堆空间的内存,保存了num
SwiftTest`getFn():
0x100004050 <+0>: pushq %rbp
0x100004051 <+1>: movq %rsp, %rbp
0x100004054 <+4>: subq $0x10, %rsp
0x100004058 <+8>: movq $0x0, -0x8(%rbp)
0x100004060 <+16>: leaq 0x40e9(%rip), %rdi
0x100004067 <+23>: movl $0x18, %esi
0x10000406c <+28>: movl $0x7, %edx
0x100004071 <+33>: callq 0x100007718 ; symbol stub for: swift_allocObject
0x100004076 <+38>: movq %rax, %rdi
0x100004079 <+41>: movq %rdi, -0x10(%rbp)
0x10000407d <+45>: movq %rdi, %rax
0x100004080 <+48>: addq $0x10, %rax
0x100004084 <+52>: movq %rax, -0x8(%rbp)
0x100004088 <+56>: movq $0x0, 0x10(%rdi)
-> 0x100004090 <+64>: callq 0x10000777e ; symbol stub for: swift_retain
0x100004095 <+69>: movq -0x10(%rbp), %rdi
0x100004099 <+73>: callq 0x100007778 ; symbol stub for: swift_release
0x10000409e <+78>: movq -0x10(%rbp), %rdx
0x1000040a2 <+82>: leaq 0x167(%rip), %rax ; partial apply forwarder for plus(Swift.Int) -> Swift.Int at <compiler-generated>
0x1000040a9 <+89>: addq $0x10, %rsp
0x1000040ad <+93>: popq %rbp
0x1000040ae <+94>: retq
如果创建另一个fn变量,执行结果如下:
var fn1 = getFn()
var fn2 = getFn()
print(fn1(1)) // 1
print(fn2(2)) // 2
print(fn1(3)) // 4
print(fn2(4)) // 6
因为fn1和fn2都分配的新的堆空间地址。
总结:
可以把闭包想象成是一个类的实例对象
内存在堆空间
捕获的局部变量/常量就是对象的成员(存储属性)
组成闭包的函数就是类内部定义的方法
注意
自动闭包
/// 如果第一个参数大于0,返回第一个数。否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
@autoclosure 会自动将第二个参数封装成闭包表达式{ 20 },如果如果第一个参数条件成立,就不会调用第二个参数闭包
@autoclosure 只支持 ( ) -> T 格式的参数
@autoclosure 并非只支持最右一个参数
空合并运算符 ?? 使用了 @autoclosure 技术