闭包
闭包表达式
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)
可以这样定义一个闭包表达式
{
(参数列表) -> 返回值类型 in
函数体代码
}
闭包表达式的简写
定义一个方法
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
})
可以将类型省略
exec(v1: 10, v2: 20, fn: {
v1, v2 in return v1 + v2
})
也可以将return 省略
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
使用美元符号$ 代替参数
exec(v1: 10, v2: 20, fn: {$0 + $1})
最终可以省略参数, 直接使用加号
exec(v1: 10, v2: 20, fn: +)
尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参, 使用尾随闭包可以增强函数可读性
尾随闭包是一个被书写在函数调用括号外面的闭包表达式
如果闭包表达式是函数的唯一实参, 而且使用了尾随闭包的语法, 就不需要再函数名后边加圆括号了
exec(v1: 10, v2: 20) { v1, v2 in
v1 + v2
}
exec(v1: 10, v2: 20) { $0 + $1 }
唯一实参
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec(fn: {
$0 + $1
})
exec(fn: {
$0 + $1
})
exec {
$0 + $1
}
swift 中的数组排序sort 函数
@inlinable public func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]
let nums = [11, 2, 6, 5, 18, 45, 99, 65]
// 自己定一个比较
func cmp(i1: Int, i2: Int) -> Bool {
// 值比较大的放在左边
return i1 > i2
}
let arr = nums.sorted(by: cmp(i1:i2:))
print("\(#line) ~~~~~~ \(#function) ~~~~~~ \(arr)")
sort 传入参数的简写
nums.sorted(by:{
(i1: Int, i2: Int) -> Bool in
return i1 > i2
})
nums.sorted(by: {
i1, i2 in
i1 > i2
})
nums.sorted(by: {
$0 > $1
})
nums.sorted(by: >)
nums.sorted() {
$0 > $1
}
nums.sorted {
$0 > $1
}
闭包
一个函数和它所捕获的变量\常量环境组合起来, 称为闭包
func getFn() -> (Int) -> Int {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus(_:)
}
var fn1 = getFn()
fn1(1)
fn1(2)
fn1(3)
fn1(4)
// 1 3 6 10
fn 捕获了变量num
如果不捕获num, 直接返回i, 调用函数, 则为以下结果
func getFn() -> (Int) -> Int {
var num = 0
func plus(_ i: Int) -> Int {
return i
}
return plus(_:)
}
rax 通常保存返回值
读取rax 值
register read rax
rax = 0x0000000100003df0 TestSwift`plus(Swift.Int) -> Swift.Int at main.swift:19
恢复代码, 返回num, 则汇编为
alloc 申请一块内存, 将num 拷贝到堆中
看下rax 返回值
可以看到x/5xg 0x0000000103827eb0
第三组中为0x0000000000000001
, 初始化为了1
.
继续跳过一个断点, 赋值为了0x0000000000000003
(lldb) x/5xg 0x0000000103827eb0
0x103827eb0: 0x0000000100004018 0x0000000200000003
0x103827ec0: 0x0000000000000003 0x0000000000000000
0x103827ed0: 0x0000000000000000
如何看到alloc 申请了多少内存空间给fn 呢?
需要24 个字节存储数据, 但是真正给的空间为32 字节, 16 的倍数
可以将闭包想想成一个类的实例对象
- 内存在堆空间
- 捕获的局部变量就是对象的成员
- 组成闭包的函数就是类内部定义的方法
class Closure {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return i
}
}
var cs1 = Closure()
cs1.plus(1)
调用到rax, 即为getFn 函数地址, jmp 为plus 地址0x100003ed0
每次调用fn1, 调用的都为plus, 同一个函数地址, 第三个字节存储的是num 的地址
如果num 为全局变量, 则
如果如下代码
typealias Fn = (Int) -> (Int, Int)
func getFns() -> (Fn, Fn) {
var num1 = 0
var num2 = 0
func plus(_ i: Int) -> (Int, Int) {
num1 += i
num2 += i << 1
return (num1, num2)
}
func minus(_ i: Int) -> (Int, Int) {
num1 -= i
num2 -= i >> 1
return (num1, num2)
}
return (plus, minus)
}
let (p, m) = getFns()
p(6) // (6, 12)
m(5) // (1, 10)
p(4) // (5, 18)
m(3) // (2, 17)
num1
和num2
, 分别alloc 一次, 给两个函数共用
自动闭包
使用@autoclosure
, 会将() -> T
格式的参数自动封装成闭包, 自动闭包也支持中间的参数, 有无自动闭包@autoclosure
, 可以构成函数重载
例如下面代码, 如果第一个参数已经大于0, 则没必要调用第二个参数的内容
// 如果第一个数大于0, 返回第一个, 否则返回第二个
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20)
getFirstPositive(-2, 20)
getFirstPositive(0, -4)
改写成下面, 将第二个参数修改为一个闭包函数
// 如果第一个参数已经大于0, 则第二个参数不必调用执行
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
在swift 中可以使用@autoclosure
, 将它封装成一个自动闭包
// 可以使用自动闭包来实现
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int? {
return v1 > 0 ? v1 : v2()
}
getFirstPositive(10, 20)
则在调用时, 最后一个参数自动封装成{20}