美文网首页
swift基础——闭包表达式和闭包

swift基础——闭包表达式和闭包

作者: 夜凉听风雨 | 来源:发表于2022-04-29 11:04 被阅读0次

一、闭包表达式

在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
})

{0 +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 <compiler-generated>
    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函数的参数,转化成十进制,也就是说把247作为参数给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 <compiler-generated>
这个函数的注释是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 <compiler-generated>
    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 <compiler-generated>
    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

三、最后

闭包表达式和闭包的区别

    1. 闭包:一个函数和它所捕获的变量\常量环境组合起来,称为闭包,本文中,plus函数和它为了存储num的值而分配的堆空间组合起来称之为闭包。
    1. 闭包表达式:用简洁语法构建内联闭包的方式,可以用闭包表达式来定义一个函数,闭包表达式的格式是这样的:{ (参数列表) -> 返回值类型 in 函数体代码}

总结

  • 闭包会对用到的局部变量进行捕获,也就是会把局部变量的值放到开辟的堆空间中,以防止局部变量销毁了导致值无法使用。捕获多个变量就会开辟多个堆空间。
  • 闭包会对用到的对象引用计数+1,防止对象被提前释放掉,不会再分配堆空间了。

相关文章

  • 闭包

    闭包 本节内容包括: 闭包表达式 尾随闭包 值捕获 闭包是引用类型 Swift 中的闭包与 C 和 Objecti...

  • Swift学习笔记(1)

    SWift学习笔记 闭包 闭包表达式 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 ...

  • swift4 闭包

    swift 闭包 闭包:swift 中 函数是闭包的一种类似于oc的闭包闭包表达式(匿名函数) -- 能够捕获上下...

  • swift 闭包与闭包表达式

    闭包与闭包表达式 在swift里闭包大家都很熟悉,相当于oc中的block。闭包表达式又是啥?很多人把闭包表达式等...

  • 使用Playground快速练习Swift语法--闭包和枚举

    闭包 定义:闭包是自包含的函数代码块,可以在代码中被传递和使用。 闭包表达式语法 Swift闭包使用{}包含,in...

  • Swift语法 -- [07 - 闭包]

    在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数 1 闭包表达式 闭包表达式 闭包表...

  • Swift 5基础语法要点整理—闭包

    闭包 闭包表达式 在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数 闭包表达式的简写...

  • Swift--闭包

    闭包的概念 Swift闭包表达式 使用闭包返回值 使用尾随闭包 捕获上下文中的变量和常量 支持闭包有两个前提1、支...

  • Swift基础之闭包

    闭包 Swift对闭包进行了简化: 利用上下文推断参数和返回值类型 隐式返回单表达式闭包,即单表达式闭包可以省略r...

  • Swift基础之闭包

    闭包 Swift对闭包进行了简化: 利用上下文推断参数和返回值类型 隐式返回单表达式闭包,即单表达式闭包可以省略r...

网友评论

      本文标题:swift基础——闭包表达式和闭包

      本文链接:https://www.haomeiwen.com/subject/mqxhyrtx.html