本文中分析两个问题: 1. Block闭包是一个引用类型 2. Block捕获外部变量
1、Block结构
1.1 IR文件分析
代码语言:javascript复制获取IR文件:swiftc -emit-ir 文件地址/main.swift > ./main.ll
func makeIncrementer() -> () -> Int{
var runningTotal = 10
//内嵌函数,也是一个闭包
func incrementer() -> Int{
runningTotal = 1
return runningTotal
}
return incrementer
}
IR文件:
- 可以看到使用
swift_allocObject
来分配堆内存
,间接证明Block是引用类型
. - 但是不是很直观。
1.2 结构图
1.3 代码结构
一个外部变量:
代码语言:javascript复制struct FuntionData<T>{
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<Box<T>>
}
struct Box<T> {
var refCounted: HeapObject
var value: UnsafePointer<Box<T>>
}
struct HeapObject{
var type: UnsafeRawPointer
var refCount: UInt64
}
2、 Block结构仿写
一个外部变量时
代码语言:javascript复制struct FuntionData<T>{
var ptr: UnsafeRawPointer
var captureValue: UnsafePointer<Box<T>>
}
struct Box<T> {
var refCounted: HeapObject
var value: UnsafePointer<Box<T>>
}
struct HeapObject{
var type: UnsafeRawPointer
var refCount: UInt64
}
//闭包的结构体,方便获取闭包地址
struct VoidIntFun {
var f: () ->Int
}
func makeIncrementer() -> () -> Int{
var runningTotal = 10
//内嵌函数,也是一个闭包
func incrementer() -> Int{
runningTotal = 1
return runningTotal
}
return incrementer
}
let makeInc = VoidIntFun(f: makeIncrementer())
let ptr = UnsafeMutablePointer<VoidIntFun>.allocate(capacity: 1)
//初始化的内存空间
ptr.initialize(to: makeInc)
//将ptr重新绑定内存
let ctx = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee }
print(ctx.ptr)
print(ctx.captureValue.pointee)
输出:
- 不论外部变量是是否发生修改,都将包装成一个
Box<T>
的结构体
二个外部变量时
代码语言:javascript复制func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 12
func incrementer() -> Int {
runningTotal = amount
return runningTotal
}
return incrementer
}
输出:
- 如果是两个变量,其中
变量二
发生了修改(相当于OC中的__block
),会包装成对象并存到捕获列表
;
如果是这样:
代码语言:javascript复制func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 12
func incrementer() -> Int {
return runningTotal amount
}
return incrementer
}
输出:
- 如果没有发生变化,就直接引用
值
,并不会进行引用类型的包装;
总结
- 引用单个变量时,不论当前变量在Block是否发生了变化,都会被包装成对象,存在
captureValue捕获列表里
- 多个变量时:
-
发生变化的外部变量进行对象包装,然后将指针地址存在捕获列表里
. -
没有修改的变量就会直接保存变量的值
;
-
- 相比之下Swift中的Block捕获方式更加简洁,但是对编译器的要求就会更高;