一、闭包表达式
在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数
-------------func定义----------------
func sum(a:Int, b:Int) -> Int {
return a + b
}
let num = sum(10, 20)
------------ 闭包定义-------------------
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
let num = fn(10, 20)
--------------匿名闭包----------------
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
闭包表达式格式如下,由{ }包裹,用in分隔开函数体代码和其他部分
{
(参数列表) -> 返回值类型 in
函数体代码
}
闭包表达式简写
定义一个函数,传入2个Int型参数和一个闭包类型参数
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
完整的调用
exec(v1: 10, v2: 20, fn: { (a: Int, b: Int) in
return a + b
})
省略闭包的类型,因为定义的时候已经声明了闭包的2个参数类型是Int,所以这里可以省略参数的类型,编译器仍然知道它的类型。
exec(v1: 10, v2: 20, fn: { (a, b) in
return a + b
})
可以省略掉包裹参数的小括号
exec(v1: 10, v2: 20, fn: { a, b in
return a + b
})
当函数体里只有一行代码时,可以省略掉return
exec(v1: 10, v2: 20, fn: { a, b in
a + b
})
可以直接省略参数,用$n来表示第n个参数,$0 + $1就表示返回第一个参数加第二个参数值
exec(v1: 10, v2: 20, fn: {
$0 + $1
})
{1} 可以直接省略掉所有的参数和{}只保留一个运算符。
exec(v1: 10, v2: 20, fn: +)
尾随闭包
- 如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
同样定义一个函数,最后一个参数声明为闭包类型
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
使用尾随闭包
exec(v1: 10, v2: 20) { (a: Int, b: Int) in
return a + b
}
同样可以简写
exec(v1: 10, v2: 20) { a, b in
return a + b
}
继续简写
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
完整尾随闭包
exec() { (a: Int, b:Int) in
return a + b
}
省略掉函数名后的圆括号
exec { (a: Int, b: Int) in
return a + b
}
简写
exec { a, b in
return a + b
}
终极简写
exec {
$0 + $1
}
二、闭包
定义:
- 一个函数和它所捕获的变量\常量环境组合起来,称为闭包
1.一般指定义在函数内部的函数
2.一般它捕获的是外层函数的局部变量\常量
先来看一个闭包
typealias Fn = (Int) -> Int
func getFn() -> Fn {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
return plus
}
var fn = getFn()
fn(1) // 1
fn(2) // 3
fn(3) // 6
思考:为什么num会累加呢?
因为闭包会捕获变量/常量,虽然在getFn函数调用完后,num就已经被回收了。但是闭包里有使用num,那么它就会捕获num,在堆空间开辟一块空间存储num的值。所以fn可以一直访问同一个num。
汇编证明
getFn()
的汇编代码是下面这个样子
swiftTest`getFn():
0x100003d60 <+0>: pushq %rbp
0x100003d61 <+1>: movq %rsp, %rbp
0x100003d64 <+4>: subq $0x10, %rsp
0x100003d68 <+8>: movq $0x0, -0x8(%rbp)
0x100003d70 <+16>: leaq 0x2a1(%rip), %rdi
0x100003d77 <+23>: movl $0x18, %esi
0x100003d7c <+28>: movl $0x7, %edx
0x100003d81 <+33>: callq 0x100003f14 ; symbol stub for: swift_allocObject
-> 0x100003d86 <+38>: movq %rax, %rdi
0x100003d89 <+41>: movq %rdi, -0x10(%rbp)
0x100003d8d <+45>: movq %rdi, %rax
0x100003d90 <+48>: addq $0x10, %rax
0x100003d94 <+52>: movq %rax, -0x8(%rbp)
0x100003d98 <+56>: movq $0x0, 0x10(%rdi)
0x100003da0 <+64>: callq 0x100003f32 ; symbol stub for: swift_retain
0x100003da5 <+69>: movq -0x10(%rbp), %rdi
0x100003da9 <+73>: callq 0x100003f2c ; symbol stub for: swift_release
0x100003dae <+78>: movq -0x10(%rbp), %rdx
0x100003db2 <+82>: leaq 0x147(%rip), %rax ; partial apply forwarder for plus(Swift.Int) -> Swift.Int at
0x100003db9 <+89>: addq $0x10, %rsp
0x100003dbd <+93>: popq %rbp
0x100003dbe <+94>: retq
现在我们来观察一下这个汇编的第+33行
callq 0x10000543a
,这句汇编的注释是symbol stub for: swift_allocObject
,也就是说这句汇编开辟了堆空间。因为函数返回值是存储在rax寄存器
中的,也就是说现在的rax寄存器
存放的是开辟的这段堆空间。我们用LLDB命令
register read rax
得到了这段堆空间的地址是rax = 0x0000000100742ac0
,然后我们用LLDB命令x/5xg
来查看一下这段堆空间到底存放了什么,存放的数据如下:
(lldb) x/5g 0x0000000100742ac0
0x100742ac0: 0x0000000100004018 0x0000000000000003
0x100742ad0: 0x00007ff80f69000c 0x00007ff80f69a67d
0x100742ae0: 0x0000000000000000
目前来看这块堆空间除了前16个字节以后存储的都是乱的数据,这没问题,因为刚开辟的空间,里面还未赋值就是乱的。
使用si
命令,一直往下走,一直走到+56行0x100003d98 <+56>: movq $0x0, 0x10(%rdi)
之后,再打印这块地址查看,发现第三个8字节为0了。+56行就是将0赋值给0x10(%rdi)
,而%rdi
存的就是这块堆内存地址,0x10(%rdi)
就是堆内存地址第16个字节。
(lldb) x/5g 0x0000000100742ac0
0x100742ac0: 0x0000000100004018 0x0000000000000003
0x100742ad0: 0x0000000000000000 0x00007ff80f69a67d
0x100742ae0: 0x0000000000000000
我们大胆猜测一下,从输出结果是1、3、6
可以看出来,访问的num
是同一个num
,所以很有可能是开辟了一段堆空间来存放num变量
的值,也就是把num
的值复制了一份放到了堆空间,方便以后的访问。而plus函数
外的num
是局部变量,getFn函数
一结束就被回收了。
我们在plus函数内部再打一个断点,观察一下每次num = num + 1
后 ,刚才那段堆空间的值是否发生了变化
调用fn(1)
后,堆空间的数据变成了下面的样子
(lldb) x/5g 0x0000000100742ac0
0x100742ac0: 0x0000000100004018 0x0000000000000003
0x100742ad0: 0x0000000000000001 0x00007ff80f69a67d
0x100742ae0: 0x0000000000000000
调用fn(2)
后,堆空间的数据变成了下面的样子
(lldb) x/5g 0x0000000100742ac0
0x100742ac0: 0x0000000100004018 0x0000000000000003
0x100742ad0: 0x0000000000000003 0x00007ff80f69a67d
0x100742ae0: 0x0000000000000000
调用fn(3)
后,堆空间的数据变成了下面的样子
(lldb) x/5g 0x0000000100742ac0
0x100742ac0: 0x0000000100004018 0x0000000000000003
0x100742ad0: 0x0000000000000006 0x00007ff80f69a67d
0x100742ae0: 0x0000000000000000
从上面可以看出来我们的
1、3、6
,确实在这段堆空间里,也就证实了我们的想法,形成闭包之后getFn()
内部会开辟一段堆空间,用来存放捕获的变量。那么这段堆空间究竟有多大呢,首先我们知道堆空间分配的内存是以16字节为单位的,也就是说是16的倍数,然后我们观察
callq 0x100003f14
分配堆空间的前两句汇编:movl $0x18, %esi,$0x7, %edx
,我们以前说过rsi寄存器
、rdx寄存器
都是用来存放参数的,而esi寄存器
不就是rsi寄存器
的其中4个字节的空间嘛,所以esi寄存器
中存放的0x18
就是要传给swift_allocObject
函数的参数,同理,edx寄存器
中存放的0x7
也是swift_allocObject
函数的参数,转化成十进制,也就是说把24
和7
作为参数给swift_allocObject
函数,可以直接告诉大家,这里的24
就是堆空间实际占用的字节数,由于堆空间的内存必须是16
的倍数,所以这块堆空间一共分配了32
个字节。前8个字节存储的是类型信息,第二个8字节存储的是引用计数,后面才是存储的值。
深入探究闭包原理
var fn = getFn()
这句代码做了什么?
我们在这句下面打上断点,查看一下汇编代码:
swiftTest`main:
0x100003c90 <+0>: pushq %rbp
0x100003c91 <+1>: movq %rsp, %rbp
0x100003c94 <+4>: pushq %r13
0x100003c96 <+6>: subq $0x58, %rsp
0x100003c9a <+10>: callq 0x100003d60 ; swiftTest.getFn() -> (Swift.Int) -> Swift.Int at main.swift:12
0x100003c9f <+15>: leaq 0x439a(%rip), %rdi ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003ca6 <+22>: xorl %ecx, %ecx
0x100003ca8 <+24>: movq %rax, 0x4391(%rip) ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003caf <+31>: movq %rdx, 0x4392(%rip) ; swiftTest.fn : (Swift.Int) -> Swift.Int + 8
-> 0x100003cb6 <+38>: leaq -0x20(%rbp), %rsi
0x100003cba <+42>: movl $0x20, %edx
0x100003cbf <+47>: callq 0x100003f1a ; symbol stub for: swift_beginAccess
0x100003cc4 <+52>: movq 0x4375(%rip), %rax ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003ccb <+59>: movq %rax, -0x58(%rbp)
0x100003ccf <+63>: movq 0x4372(%rip), %r13 ; swiftTest.fn : (Swift.Int) -> Swift.Int + 8
0x100003cd6 <+70>: movq %r13, -0x50(%rbp)
0x100003cda <+74>: movq %r13, %rdi
0x100003cdd <+77>: callq 0x100003f32 ; symbol stub for: swift_retain
0x100003ce2 <+82>: leaq -0x20(%rbp), %rdi
0x100003ce6 <+86>: callq 0x100003f26 ; symbol stub for: swift_endAccess
0x100003ceb <+91>: movq -0x58(%rbp), %rax
0x100003cef <+95>: movl $0xa, %edi
0x100003cf4 <+100>: callq *%rax
0x100003cf6 <+102>: movq -0x50(%rbp), %rdi
0x100003cfa <+106>: callq 0x100003f2c ; symbol stub for: swift_release
0x100003cff <+111>: leaq 0x433a(%rip), %rdi ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003d06 <+118>: xorl %eax, %eax
0x100003d08 <+120>: movl %eax, %ecx
0x100003d0a <+122>: leaq -0x38(%rbp), %rsi
0x100003d0e <+126>: movl $0x20, %edx
0x100003d13 <+131>: callq 0x100003f1a ; symbol stub for: swift_beginAccess
0x100003d18 <+136>: movq 0x4321(%rip), %rax ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003d1f <+143>: movq %rax, -0x48(%rbp)
0x100003d23 <+147>: movq 0x431e(%rip), %r13 ; swiftTest.fn : (Swift.Int) -> Swift.Int + 8
0x100003d2a <+154>: movq %r13, -0x40(%rbp)
0x100003d2e <+158>: movq %r13, %rdi
0x100003d31 <+161>: callq 0x100003f32 ; symbol stub for: swift_retain
0x100003d36 <+166>: leaq -0x38(%rbp), %rdi
0x100003d3a <+170>: callq 0x100003f26 ; symbol stub for: swift_endAccess
0x100003d3f <+175>: movq -0x48(%rbp), %rax
0x100003d43 <+179>: movl $0x14, %edi
0x100003d48 <+184>: callq *%rax
0x100003d4a <+186>: movq -0x40(%rbp), %rdi
0x100003d4e <+190>: callq 0x100003f2c ; symbol stub for: swift_release
0x100003d53 <+195>: xorl %eax, %eax
0x100003d55 <+197>: addq $0x58, %rsp
0x100003d59 <+201>: popq %r13
0x100003d5b <+203>: popq %rbp
0x100003d5c <+204>: retq
断点上面的+24行代码是将%rax
的值给到0x4391(%rip)
,之前我们说过0x4391(%rip)
这种存储的都是全局变量,%rax
存储的是函数返回值。所以%rax
里面装的是getFn函数
返回的plus函数
地址,0x4391(%rip)
其实就是全局变量fn,也就是将plus函数地址存到fn变量的地址空间里。
+31行代码是将%rdx
里的内容赋值给0x4392(%rip)
,%rdx里存的其实就是捕获的num的堆区地址。通过计算得到0x4391(%rip)
和0x4392(%rip)
相差8个字节,又通过MemoryLayout.stride(ofValue: fn)
获取到fn变量实际占用空间是16个字节,结合这2点我们猜测fn里16个字节前8个字节存的是函数plus地址,后8个字节存的是捕获的num地址。
通过打印0x4391(%rip)
的地址内存可以看到内部确实有存储2个地址。
(lldb) x/4g 0x100008040
0x100008040: 0x0000000100003f00 0x000000010193eb00
0x100008050: 0x0000000000000000 0x0000000000000000
fn(1)
这句代码做了什么呢?
汇编代码第+100行、+184行、+268行都调用了 callq *%rax
, callq命令就是执行函数,rax内部存的就是plus函数地址。
进入这个函数内部:
swiftTest`partial apply for plus #1 (_:) in getFn():
-> 0x100003f00 <+0>: pushq %rbp
0x100003f01 <+1>: movq %rsp, %rbp
0x100003f04 <+4>: movq %r13, %rsi
0x100003f07 <+7>: popq %rbp
0x100003f08 <+8>: jmp 0x100003de0 ; plus(Swift.Int) -> Swift.Int at main.swift:15
最后又调用jmp 0x100003de0
,跳转到另一个函数了。继续进入这个函数,发现它就是plus函数。
swiftTest`plus #1 (_:) in getFn():
-> 0x100003de0 <+0>: pushq %rbp
0x100003de1 <+1>: movq %rsp, %rbp
0x100003de4 <+4>: subq $0xa0, %rsp
0x100003deb <+11>: movq %rsi, -0x78(%rbp)
0x100003def <+15>: movq %rdi, -0x68(%rbp)
0x100003df3 <+19>: xorl %esi, %esi
0x100003df5 <+21>: leaq -0x8(%rbp), %rdi
0x100003df9 <+25>: movl $0x8, %edx
0x100003dfe <+30>: callq 0x100003f0e ; symbol stub for: memset
0x100003e03 <+35>: xorl %esi, %esi
0x100003e05 <+37>: leaq -0x10(%rbp), %rdi
0x100003e09 <+41>: movl $0x8, %edx
0x100003e0e <+46>: callq 0x100003f0e ; symbol stub for: memset
0x100003e13 <+51>: movq -0x78(%rbp), %rdi
0x100003e17 <+55>: movq -0x68(%rbp), %rax
0x100003e1b <+59>: xorl %ecx, %ecx
0x100003e1d <+61>: movq %rax, -0x8(%rbp)
0x100003e21 <+65>: addq $0x10, %rdi
0x100003e25 <+69>: movq %rdi, -0x80(%rbp)
0x100003e29 <+73>: movq %rdi, -0x10(%rbp)
0x100003e2d <+77>: leaq -0x28(%rbp), %rsi
0x100003e31 <+81>: movl $0x20, %edx
0x100003e36 <+86>: callq 0x100003f1a ; symbol stub for: swift_beginAccess
0x100003e3b <+91>: movq -0x78(%rbp), %rsi
0x100003e3f <+95>: movq 0x10(%rsi), %rax
0x100003e43 <+99>: movq %rax, -0x70(%rbp)
0x100003e47 <+103>: leaq -0x28(%rbp), %rdi
0x100003e4b <+107>: callq 0x100003f26 ; symbol stub for: swift_endAccess
0x100003e50 <+112>: movq -0x70(%rbp), %rax
0x100003e54 <+116>: movq -0x68(%rbp), %rdi
0x100003e58 <+120>: addq %rdi, %rax
0x100003e5b <+123>: movq %rax, -0x60(%rbp)
0x100003e5f <+127>: seto %al
0x100003e62 <+130>: testb $0x1, %al
0x100003e64 <+132>: jne 0x100003eef ; <+271> [inlined] Swift runtime failure: arithmetic overflow at main.swift:16:19
0x100003e6a <+138>: movq -0x80(%rbp), %rdi
0x100003e6e <+142>: xorl %eax, %eax
0x100003e70 <+144>: movl %eax, %ecx
0x100003e72 <+146>: movq %rcx, -0x98(%rbp)
0x100003e79 <+153>: leaq -0x40(%rbp), %rsi
0x100003e7d <+157>: movq %rsi, -0xa0(%rbp)
0x100003e84 <+164>: movl $0x21, %edx
0x100003e89 <+169>: callq 0x100003f1a ; symbol stub for: swift_beginAccess
0x100003e8e <+174>: movq -0x60(%rbp), %rcx
0x100003e92 <+178>: movq -0xa0(%rbp), %rdi
0x100003e99 <+185>: movq -0x80(%rbp), %rax
0x100003e9d <+189>: movq %rcx, (%rax)
0x100003ea0 <+192>: callq 0x100003f26 ; symbol stub for: swift_endAccess
0x100003ea5 <+197>: movq -0x98(%rbp), %rcx
0x100003eac <+204>: movq -0x80(%rbp), %rdi
0x100003eb0 <+208>: leaq -0x58(%rbp), %rsi
0x100003eb4 <+212>: movq %rsi, -0x90(%rbp)
0x100003ebb <+219>: movl $0x20, %edx
0x100003ec0 <+224>: callq 0x100003f1a ; symbol stub for: swift_beginAccess
0x100003ec5 <+229>: movq -0x80(%rbp), %rax
0x100003ec9 <+233>: movq -0x90(%rbp), %rdi
0x100003ed0 <+240>: movq (%rax), %rax
0x100003ed3 <+243>: movq %rax, -0x88(%rbp)
0x100003eda <+250>: callq 0x100003f26 ; symbol stub for: swift_endAccess
0x100003edf <+255>: movq -0x88(%rbp), %rax
0x100003ee6 <+262>: addq $0xa0, %rsp
0x100003eed <+269>: popq %rbp
0x100003eee <+270>: retq
0x100003eef <+271>: ud2
所以说变量fn前8个字节存的是一个函数地址,但是这个函数是一个中转站,最终会跳转到plus函数内。
在上文中getFn
函数汇编代码里第+82行,返回的是这么个函数:
0x100003db2 <+82>: leaq 0x147(%rip), %rax ; partial apply forwarder for plus(Swift.Int) -> Swift.Int at
这个函数的注释是partial apply forwarder for plus
,这个也证明getFn汇编返回的并不是真正的plus函数,而是一个最终指向plus的函数。
闭包使用全局变量
如果闭包里使用一个全局的变量,闭包并不会捕获它。因为捕获是为了让局部变量不被释放,从而在闭包使用时能访问到。而全局变量本身可以直接访问,所以不会捕获。
闭包使用多个局部变量
如果闭包里使用了多个局部变量,那么就会开辟多个堆空间,来存储它们。
多个闭包使用同一个局部变量
如果多个闭包都使用了一个局部变量,也只会开辟一个堆空间,它们使用的是同一个数据。
typealias Fn = (Int) -> Int
func getFn() -> (Fn, Fn) {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
func reduce(_ j: Int) -> Int {
num -= j
return num
}
return (plus, reduce)
}
var (fn1, fn2) = getFn()
print(fn1(1)) // 1
print(fn2(1)) // 0
闭包内使用局部的原本就在堆空间的变量
上文中一直使用的都是普通的Int型变量,那么如果使用一个类对象,它原本就会在堆空间开辟一个空间,闭包使用时会发生什么?
代码:
可以看到闭包使用的p.age是同一个。难道这里闭包也为p对象再次开辟了一个堆空间?继续往下查看汇编代码。
class Person {
var age: Int = 0
}
typealias Fn = (Int) -> Int
func getFn() -> Fn {
let p = Person()
func plus(_ i: Int) -> Int {
p.age += i
return p.age
}
return plus
}
var fn = getFn()
fn(1) // 1
fn(2) // 3
getFn方法汇编代码:
可以看到这里只有第+30行0x100003cde <+30>: callq 0x100003c70 ; swiftTest.Person.__allocating_init() -> swiftTest.Person at main.swift:10
开辟过一次空间,也就是let p = Person()
时开辟的堆空间,闭包捕获时并没有再为它开辟堆空间。那后面闭包是如何使用这个p对象的呢?
swiftTest`getFn():
0x100003cc0 <+0>: pushq %rbp
0x100003cc1 <+1>: movq %rsp, %rbp
0x100003cc4 <+4>: pushq %r13
0x100003cc6 <+6>: subq $0x18, %rsp
0x100003cca <+10>: movq $0x0, -0x10(%rbp)
0x100003cd2 <+18>: xorl %eax, %eax
0x100003cd4 <+20>: movl %eax, %edi
0x100003cd6 <+22>: callq 0x100003d10 ; type metadata accessor for swiftTest.Person at
0x100003cdb <+27>: movq %rax, %r13
0x100003cde <+30>: callq 0x100003c70 ; swiftTest.Person.__allocating_init() -> swiftTest.Person at main.swift:10
0x100003ce3 <+35>: movq %rax, %rdi
0x100003ce6 <+38>: movq %rdi, -0x18(%rbp)
0x100003cea <+42>: movq %rdi, -0x10(%rbp)
-> 0x100003cee <+46>: callq 0x100003e0c ; symbol stub for: swift_retain
0x100003cf3 <+51>: movq -0x18(%rbp), %rdi
0x100003cf7 <+55>: callq 0x100003e06 ; symbol stub for: swift_release
0x100003cfc <+60>: movq -0x18(%rbp), %rdx
0x100003d00 <+64>: leaq 0xc9(%rip), %rax ; partial apply forwarder for plus(Swift.Int) -> Swift.Int at
0x100003d07 <+71>: addq $0x18, %rsp
0x100003d0b <+75>: popq %r13
0x100003d0d <+77>: popq %rbp
0x100003d0e <+78>: retq
main函数汇编代码:
可以看到第+24行0x100003998 <+24>: movq %rax, 0x4811(%rip)
将%rax内容赋值给了0x4811(%rip),也就是将plus函数赋值给了变量fn的前8个字节。
第+31行0x10000399f <+31>: movq %rdx, 0x4812(%rip)
将%rdx赋值给0x4812(%rip),%rdx寄存器在getFn方法里存储的其实就是Person对象的地址。也就是将Person对象p的地址赋值给了fn的后8个字节。
通过register read rdx
命令打印出来rdx的地址和之前getFn内Person()
开辟出来的地址一样。
结论:闭包在使用堆区的对象时仅仅只是引用了它的地址。
swiftTest`main:
0x100003980 <+0>: pushq %rbp
0x100003981 <+1>: movq %rsp, %rbp
0x100003984 <+4>: pushq %r13
0x100003986 <+6>: subq $0x58, %rsp
0x10000398a <+10>: callq 0x100003cc0 ; swiftTest.getFn() -> (Swift.Int) -> Swift.Int at main.swift:16
0x10000398f <+15>: leaq 0x481a(%rip), %rdi ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x100003996 <+22>: xorl %ecx, %ecx
0x100003998 <+24>: movq %rax, 0x4811(%rip) ; swiftTest.fn : (Swift.Int) -> Swift.Int
0x10000399f <+31>: movq %rdx, 0x4812(%rip) ; swiftTest.fn : (Swift.Int) -> Swift.Int + 8
-> 0x1000039a6 <+38>: leaq -0x20(%rbp), %rsi
0x1000039aa <+42>: movl $0x20, %edx
0x1000039af <+47>: callq 0x100003df4 ; symbol stub for: swift_beginAccess
三、最后
闭包表达式和闭包的区别
-
- 闭包:一个函数和它所捕获的变量\常量环境组合起来,称为闭包,本文中,plus函数和它为了存储num的值而分配的堆空间组合起来称之为闭包。
-
- 闭包表达式:用简洁语法构建内联闭包的方式,可以用闭包表达式来定义一个函数,闭包表达式的格式是这样的:{ (参数列表) -> 返回值类型 in 函数体代码}
总结
- 闭包会对用到的局部变量进行捕获,也就是会把局部变量的值放到开辟的堆空间中,以防止局部变量销毁了导致值无法使用。捕获多个变量就会开辟多个堆空间。
- 闭包会对用到的对象引用计数+1,防止对象被提前释放掉,不会再分配堆空间了。