1.闭包定义
闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作。闭包是一个引用类型。
关键字:捕获,常量变量,引用。
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()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
先猜一下会打印什么,和你想象中的结果一样吗? getFn是一个函数,num是一个局部变量,为什么会累加呢?
汇编分析:
断点放在return plus处。
SwiftTest`getFn():
0x100002a40 <+0>: pushq %rbp
0x100002a41 <+1>: movq %rsp, %rbp
0x100002a44 <+4>: subq $0x20, %rsp
0x100002a48 <+8>: movq $0x0, -0x8(%rbp)
0x100002a50 <+16>: leaq 0x15d1(%rip), %rdi
0x100002a57 <+23>: movl $0x18, %esi
0x100002a5c <+28>: movl $0x7, %edx
-> 0x100002a61 <+33>: callq 0x100003c8e ; symbol stub for: swift_allocObject
0x100002a66 <+38>: movq %rax, %rcx
-> 0x100002a69 <+41>: addq $0x10, %rcx
0x100002a6d <+45>: movq %rcx, -0x8(%rbp)
0x100002a71 <+49>: movq $0x0, 0x10(%rax)
-> 0x100002a79 <+57>: movq %rax, %rdi
0x100002a7c <+60>: movq %rax, -0x10(%rbp)
0x100002a80 <+64>: callq 0x100003cac ; symbol stub for: swift_retain
0x100002a85 <+69>: movq -0x10(%rbp), %rdi
0x100002a89 <+73>: movq %rax, -0x18(%rbp)
0x100002a8d <+77>: callq 0x100003ca6 ; symbol stub for: swift_release
0x100002a92 <+82>: leaq 0x137(%rip), %rax ; partial apply forwarder for plus #1 (Swift.Int) -> Swift.Int in SwiftTest.getFn() -> (Swift.Int) -> Swift.Int at <compiler-generated>
0x100002a99 <+89>: movq -0x10(%rbp), %rdx
0x100002a9d <+93>: addq $0x20, %rsp
0x100002aa1 <+97>: popq %rbp
0x100002aa2 <+98>: retq
-
callq 0x100003c8e ; symbol stub for: swift_allocObject,可以看到是getFun函数里面开辟的堆空间。并且返回%rax
-
movq $0x0, 0x10(%rax),把0放到%rax+0x10,也就是rax的第16个字节位置
这时候,我们先记录下来rax的内存地址,和内存地址的值
(lldb) register read rax
rax = 0x0000000100522450
(lldb) x/4xg 0x0000000100522450
0x100522450: 0x0000000100004028 0x0000000000000003
0x100522460: 0x0000000000000000 0x00007fff2384f465
可以看出来,rax的16个字节处为0。
然后断点放在return num;
SwiftTest`plus #1 (_:) in getFn():
0x100002ad0 <+0>: pushq %rbp
0x100002ad1 <+1>: movq %rsp, %rbp
0x100002ad4 <+4>: subq $0x90, %rsp
0x100002adb <+11>: xorl %eax, %eax
0x100002add <+13>: movl %eax, %ecx
0x100002adf <+15>: xorl %eax, %eax
0x100002ae1 <+17>: leaq -0x8(%rbp), %rdx
0x100002ae5 <+21>: movq %rdi, -0x48(%rbp)
0x100002ae9 <+25>: movq %rdx, %rdi
0x100002aec <+28>: movq %rsi, -0x50(%rbp)
0x100002af0 <+32>: movl %eax, %esi
0x100002af2 <+34>: movl $0x8, %edx
0x100002af7 <+39>: movq %rdx, -0x58(%rbp)
0x100002afb <+43>: movq %rcx, -0x60(%rbp)
0x100002aff <+47>: movl %eax, -0x64(%rbp)
0x100002b02 <+50>: callq 0x100003c46 ; symbol stub for: memset
0x100002b07 <+55>: leaq -0x10(%rbp), %rcx
0x100002b0b <+59>: movq %rcx, %rdi
0x100002b0e <+62>: movl -0x64(%rbp), %esi
0x100002b11 <+65>: movq -0x58(%rbp), %rdx
0x100002b15 <+69>: callq 0x100003c46 ; symbol stub for: memset
0x100002b1a <+74>: movq -0x48(%rbp), %rcx
0x100002b1e <+78>: movq %rcx, -0x8(%rbp)
0x100002b22 <+82>: movq -0x50(%rbp), %rdx
0x100002b26 <+86>: addq $0x10, %rdx
0x100002b2d <+93>: movq %rdx, -0x10(%rbp)
0x100002b31 <+97>: movq %rdx, %rdi
0x100002b34 <+100>: leaq -0x28(%rbp), %rsi
0x100002b38 <+104>: movl $0x21, %r8d
0x100002b3e <+110>: movq %rdx, -0x70(%rbp)
0x100002b42 <+114>: movq %r8, %rdx
0x100002b45 <+117>: movq -0x60(%rbp), %rcx
0x100002b49 <+121>: callq 0x100003c94 ; symbol stub for: swift_beginAccess
0x100002b4e <+126>: movq -0x48(%rbp), %rcx
0x100002b52 <+130>: movq -0x50(%rbp), %rdx
0x100002b56 <+134>: addq 0x10(%rdx), %rcx
0x100002b5a <+138>: seto %r9b
0x100002b5e <+142>: testb $0x1, %r9b
0x100002b62 <+146>: movq %rcx, -0x78(%rbp)
0x100002b66 <+150>: jne 0x100002bc0 ; <+240> [inlined] Swift runtime failure: arithmetic overflow at main.swift:393
0x100002b68 <+152>: movq -0x70(%rbp), %rax
0x100002b6c <+156>: movq -0x78(%rbp), %rcx
0x100002b70 <+160>: movq %rcx, (%rax)
-> 0x100002b73 <+163>: leaq -0x28(%rbp), %rdi
0x100002b77 <+167>: callq 0x100003ca0 ; symbol stub for: swift_endAccess
这时候,相当于运行到fn(1)里面的plus方法,我们主要看下面这段就好了
0x100002b49 <+121>: callq 0x100003c94 ; symbol stub for: swift_beginAccess
-> 0x100002b4e <+126>: movq -0x48(%rbp), %rcx
0x100002b52 <+130>: movq -0x50(%rbp), %rdx
-> 0x100002b56 <+134>: addq 0x10(%rdx), %rcx
0x100002b5a <+138>: seto %r9b
0x100002b5e <+142>: testb $0x1, %r9b
0x100002b62 <+146>: movq %rcx, -0x78(%rbp)
0x100002b66 <+150>: jne 0x100002bc0 ; <+240> [inlined] Swift runtime failure: arithmetic overflow at main.swift:393
0x100002b68 <+152>: movq -0x70(%rbp), %rax
0x100002b6c <+156>: movq -0x78(%rbp), %rcx
-> 0x100002b70 <+160>: movq %rcx, (%rax)
-> 0x100002b73 <+163>: leaq -0x28(%rbp), %rdi
0x100002b77 <+167>: callq 0x100003ca0 ; symbol stub for: swift_endAccess
movq -0x48(%rbp), %rcx
movq -0x50(%rbp), %rdx
addq 0x10(%rdx), %rcx
局部变量i=1给到% rcx,然后把%rdx+0x10的值也就是堆空间sum=0,相加,为rcx = 1
movq %rcx, (%rax)
rcx赋值给%rax的值
打印一下rax的内容
(lldb) register read rax
rax = 0x0000000100522460
(lldb) x/xg 0x0000000100522460
0x100522460: 0x0000000000000001
(lldb) x/4xg 0x0000000100522450
0x100522450: 0x0000000100004028 0x0000000200000003
0x100522460: 0x0000000000000001 0x00007fff2384f465
可以看到这里的rax的地址值0x0000000100522460 和我们之前的sum的地址值0x0000000100522450,相差了16个字节,也可以看来sum的地址值的16个字节处由0变为1,sum地址前面16个字节存储的是基础信息和引用计数。
后面的fn(2)也是这样走的流程。
结论
在闭包中,创建一个getFn(),就会开辟一次堆空间来存储捕获变量的值,var fn = getFn()走完后,函数的局部变量sum已经回收了。plus方法的sum其实是在堆空间的sum,记录这为0。
fn(1)后sum更新为1,fn(2)后sum更新为3。fn(3)后sum更新为6。fn(4)后sum更新为10。