Swift探索(七): 闭包
一:函数类型
每个函数都有种特定的函数类型,函数的类型由函数的参数类型和返回类型组成。
func addOne(_ a: Int, _ b: Int) -> Int {
return a + b
}
func addTwo(_ a: Double, _ b: Double) -> Double {
return a + b
}
var a: (Double, Double) -> Double = addTwo
上述代码中 (Double, Double) -> Double
就是函数类型
函数在 Swift
中是引用类型,也有自己的 metadata
/// The structure of function type metadata.
template <typename Runtime>
struct TargetFunctionTypeMetadata : public TargetMetadata<Runtime> {
using StoredSize = typename Runtime::StoredSize;
using Parameter = ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>;
TargetFunctionTypeFlags<StoredSize> Flags;
/// The type metadata for the result type.
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;
Parameter *getParameters() { return reinterpret_cast<Parameter *>(this + 1); }
const Parameter *getParameters() const {
return reinterpret_cast<const Parameter *>(this + 1);
}
Parameter getParameter(unsigned index) const {
assert(index < getNumParameters());
return getParameters()[index];
}
ParameterFlags getParameterFlags(unsigned index) const {
assert(index < getNumParameters());
auto flags = hasParameterFlags() ? getParameterFlags()[index] : 0;
return ParameterFlags::fromIntValue(flags);
}
StoredSize getNumParameters() const {
return Flags.getNumParameters();
}
FunctionMetadataConvention getConvention() const {
return Flags.getConvention();
}
bool isAsync() const { return Flags.isAsync(); }
bool isThrowing() const { return Flags.isThrowing(); }
bool isSendable() const { return Flags.isSendable(); }
bool isDifferentiable() const { return Flags.isDifferentiable(); }
bool hasParameterFlags() const { return Flags.hasParameterFlags(); }
bool isEscaping() const { return Flags.isEscaping(); }
bool hasGlobalActor() const { return Flags.hasGlobalActor(); }
static constexpr StoredSize OffsetToFlags = sizeof(TargetMetadata<Runtime>);
static bool classof(const TargetMetadata<Runtime> *metadata) {
return metadata->getKind() == MetadataKind::Function;
}
uint32_t *getParameterFlags() {
return reinterpret_cast<uint32_t *>(getParameters() + getNumParameters());
}
const uint32_t *getParameterFlags() const {
return reinterpret_cast<const uint32_t *>(getParameters() +
getNumParameters());
}
TargetFunctionMetadataDifferentiabilityKind<StoredSize> *
getDifferentiabilityKindAddress() {
assert(isDifferentiable());
void *previousEndAddr = hasParameterFlags()
? reinterpret_cast<void *>(getParameterFlags() + getNumParameters())
: reinterpret_cast<void *>(getParameters() + getNumParameters());
return reinterpret_cast<
TargetFunctionMetadataDifferentiabilityKind<StoredSize> *>(
llvm::alignAddr(previousEndAddr,
llvm::Align(alignof(typename Runtime::StoredPointer))));
}
TargetFunctionMetadataDifferentiabilityKind<StoredSize>
getDifferentiabilityKind() const {
if (isDifferentiable()) {
return *const_cast<TargetFunctionTypeMetadata<Runtime> *>(this)
->getDifferentiabilityKindAddress();
}
return TargetFunctionMetadataDifferentiabilityKind<StoredSize>
::NonDifferentiable;
}
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> *
getGlobalActorAddr() {
assert(hasGlobalActor());
void *endAddr =
isDifferentiable()
? reinterpret_cast<void *>(getDifferentiabilityKindAddress() + 1) :
hasParameterFlags()
? reinterpret_cast<void *>(getParameterFlags() + getNumParameters()) :
reinterpret_cast<void *>(getParameters() + getNumParameters());
return reinterpret_cast<
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> *>(
llvm::alignAddr(
endAddr, llvm::Align(alignof(typename Runtime::StoredPointer))));
}
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>
getGlobalActor() const {
if (!hasGlobalActor())
return ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>();
return *const_cast<TargetFunctionTypeMetadata<Runtime> *>(this)
->getGlobalActorAddr();
}
};
在 Swift源码 中的 Metadata.h
文件中找到 TargetFunctionTypeMetadata
的声明,可以发现 TargetFunctionTypeMetadata
是继承自 TargetMetadata
,通过前一篇文章 Mirror源码解析中我们已经分析过 TargetMetadata
他里面有一个 kind
属性。这里可以看见有个TargetFunctionTypeFlags
类型的 Flags
属性和 ResultType
属性也就是返回值类型, 还有一个连续的内存数组空间 Parameter
(存储参数类型),进入到 TargetFunctionTypeFlags
中
class TargetFunctionTypeFlags {
// If we were ever to run out of space for function flags (8 bits)
// one of the flag bits could be used to identify that the rest of
// the flags is going to be stored somewhere else in the metadata.
enum : int_type {
NumParametersMask = 0x0000FFFFU,
ConventionMask = 0x00FF0000U,
ConventionShift = 16U,
ThrowsMask = 0x01000000U,
ParamFlagsMask = 0x02000000U,
EscapingMask = 0x04000000U,
DifferentiableMask = 0x08000000U,
GlobalActorMask = 0x10000000U,
AsyncMask = 0x20000000U,
SendableMask = 0x40000000U,
// NOTE: The next bit will need to introduce a separate flags word.
};
int_type Data;
...
};
可以看到这里标识了很多类型的函数,可以通过这些字段来判断函数是什么类型。
因此可以定义出函数类型结构体如下
struct TargetFunctionTypeMetadata{
var kind: Int
var flags: Int
var arguments: ArgumentsBuffer<Any.Type>
// 获取参数数量
func numberArguments() -> Int{
return self.flags & 0x0000FFFF
}
}
struct ArgumentsBuffer<Element>{
var element: Element
mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start
}
return UnsafeBufferPointer(start: ptr, count: n)
}
}
mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
其中 arguments
与 FieldDescriptor
中的 fields
属性一样。
// 获取addTwo函数的类型
let value = type(of: addTwo)
let functionType = unsafeBitCast(value as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
print(functionType.pointee.numberArguments())
打印结果
2
二:什么是闭包
2.1. 闭包的定义
闭包是一个捕获了上下文的常量或者变量的函数。
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
var a = makeIncrementer()
根据官方文档提供的代码,我们可以看到这里定义了一个外部函数 makeIncrementer
返回类型是 () -> Int
是一个没有参数并且返回值为 Int
的函数类型,在函数中定义了一个内部函数 incrementer()
返回值为 Int
,在内部函数 incrementer()
中使用了外部函数 makeIncrementer()
的变量 runningTotal
, 外部函数 makeIncrementer()
最后返回这个内部函数 incrementer()
。var a = makeIncrementer()
即为将 incrementer()
函数赋值给变量 a
,这里外部函数 makeIncrementer()
即访问完成,而内部函数 incrementer()
在 a
调用的时候才会进行访问,因此内部函数 incrementer()
的生命周期要比外部函数 makeIncrementer()
长,对于 runningTotal
来说在外部函数 makeIncrementer()
调用完就释放了,但是内部函数还在使用 incrementer()
因此内部函数 incrementer()
将 runningTotal
捕获到内部函数内部。综上所述将 runningTotal
和 incrementer()
称之为闭包。
2.2. 闭包表达式
{ (param) -> (returnType) in
//do something
}
- 作用域(也就是大括号)
- 参数和返回值
- 函数体(
in
)之后的代码
2.3. 闭包的书写
- 闭包作为变量
var closure: (Int) -> Int = { (age: Int) in
return age
}
- 闭包声明成一个可选类型
var closure : ((Int) -> Int)?
closure = nil
- 闭包声明成一个常量
let closure: (Int) -> Int
closure = {(age: Int) in
return age
}
- 闭包作为函数的参数
func test(param : () -> Int){
print(param())
}
var age = 10
test { () -> Int in
age += 1
return age
}
三:尾随闭包
当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很⻓,我们可以通过尾随闭包的书写方式来提高代码的可读性。
func test(_ a: Int, _ b: Int, by:(_ item1: Int, _ item2: Int) -> Bool) -> Bool {
return by(a, b)
}
// 不使用尾随闭包
test(10, 20, by: {(_ item1: Int, _ item2: Int) -> Bool in
return item1 > item2
})
// 使用尾随闭包
test(10, 20) { item1, item2 in
return item1 > item2
}
其中闭包表达式是 Swift 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:
- 利用上下文推断参数和返回值类型
- 单表达式可以隐士返回,既省略
return
关键字 - 参数名称的简写(比如我们的
$0
) - 尾随闭包表达式
var array = [1, 2, 3]
array.sort(by: {(item1: Int, item2: Int) -> Bool in return item1 < item2})
array.sort(by: {(item1, item2) -> Bool in return item1 < item2})
array.sort{(item1, item2) in item1 < item2 }
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by: <)
四:捕获值
4.1 Block捕获值
- (void)testBlock {
NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld", i);
};
i += 1;
NSLog(@"before block %ld", i);
block();
NSLog(@"after block %ld", i);
}
var t = Test.init()
t.testBlock()
打印结果
before block 2
block 1
after block 2
如果想要外部的修改能够影响当前 block
内部捕获的值,我们只需要对当前的 i
添加 __block
修饰符
- (void)testBlock {
__block NSInteger i = 1;
void(^block)(void) = ^{
NSLog(@"block %ld", i);
};
i += 1;
NSLog(@"before block %ld", i);
block();
NSLog(@"after block %ld", i);
}
var t = Test.init()
t.testBlock()
打印结果
before block 2
block 2
after block 2
4.2 闭包捕获值
4.2.1 闭包捕获全局变量
将 block
换成 swift
的闭包
var i = 1
let closure = {
print("closure \(i)")
}
i += 1
print("before closure \(i)")
closure()
print("after closure \(i)")
打印结果
before closure 2
closure 2
after closure 2
发现和 OC
的 block
不一样,通过命令 swiftc main.swift -emit-sil
编译成 SIL
代码定位到 closure()
的定义
// closure #1 in
sil private [ossa] @$s4mainyycfU_ : $@convention(thin) () -> () {
bb0:
%0 = global_addr @$s4main1iSivp : $*Int // user: %32
%1 = integer_literal $Builtin.Word, 1 // user: %3
// function_ref _allocateUninitializedArray<A>(_:)
%2 = function_ref
...
} // end sil function '$s4mainyycfU_'
可以看到 这里获取的是 s4main1iSivp
的全局地址,而 s4main1iSivp
就是定义的全局变量 i
// i
sil_global hidden @$s4main1iSivp : $Int
这里是直接获取的 i
的地址中的值,因此闭包是不会捕获全局变量的。
4.2.2 闭包捕获局部变量
将这些代码放在函数当中调用
func test() {
var i = 1
let closure = {
print("closure \(i)")
}
i += 1
print("before closure \(i)")
closure()
print("after closure \(i)")
}
test()
打印结果
before closure 2
closure 2
after closure 2
编译成 SIL
代码
// closure #1 in test()
sil private [ossa] @$s4main4testyyFyycfU_ : $@convention(thin) (@guaranteed { var Int }) -> () {
// %0 "i" // user: %1
bb0(%0 : @guaranteed ${ var Int }):
%1 = project_box %0 : ${ var Int }, 0 // users: %34, %2
debug_value_addr %1 : $*Int, var, name "i", argno 1 // id: %2
%3 = integer_literal $Builtin.Word, 1 // user: %5
...
} // end sil function '$s4main4testyyFyycfU_'
可以看到这里没有再调用 i
的地址,而是通过project_box
从堆上取出变量地址,因此闭包是会捕获局部变量的。
五:闭包的本质
5.1:IR语法
通过上面的分析我们知道函数在 Swift
中是引用类型,并且有自己的 metadata
,那么闭包的本质是什么呢?通过对 SIL
文件我们看不出什么,因此我们再降一级在 IR
文件中分析。
对于 IR
的语法网上有很多教程,这里我举几个常用的例子
- 数组
[<elementnumber> x <elementtype>]
alloca [24 x i8], align 8 24个i8都是0
alloca [4 x i32] === array
elementnumber
当前元素的个数
elementtype
当前元素的类型
- 结构体
%swift.refcounted = type { %swift.type*, i64 } // %swift.type* 的指针类型,i64 64位的整型
//表示形式
%T = type {<type list>} //这种和C语言的结构体类似
{
开始 }
结束 type list
元素列表
- 指针类型
<type> *
// example
i64* //64位的整形
-
bitcase
指令
%2 = bitcast i8** %1 to i8*
指针类型的转换
-
getelementptr
指令
LLVM
中我们获取数组和结构体的成员,通过getelementptr
,语法规则如下
<result> = getelementptr <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <id
// 官方案例
struct munger_struct {
int f1;
int f2;
};
void munge(struct munger_struct *P) {
P[0].f1 = P[1].f1 + P[2].f2;
};
创建一个 Test.c
文件并将上述代码编写其中,通过 clang
指令 clang -S -fobjc-arc -emit-llvm Test.c
将 Test.c
文件编译成 IR
文件
// 结构体定义
%struct.munger_struct = type { i32, i32 }
; Function Attrs: noinline nounwind optnone ssp uwtable
define void @munge(%struct.munger_struct* %0) #0 {
// 存储结构体首地址
%2 = alloca %struct.munger_struct*, align 8
// %struct.munger_struct** 二级指针
store %struct.munger_struct* %0, %struct.munger_struct** %2, align 8
%3 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
// %struct.munger_struct 当前索引基本类型 %struct.munger_struct* 当前索引结构体地址 i64 1 当前数组Index
%4 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
// 第一个 i32 0 结构体指针偏移0字节 相当于不偏移 第二个 i32 0 获取第一个结构体成员
%5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
%6 = load i32, i32* %5, align 4
%7 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
%8 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %7, i64 2
%9 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %8, i32 0, i32 1
%10 = load i32, i32* %9, align 4
%11 = add nsw i32 %6, %10
%12 = load %struct.munger_struct*, %struct.munger_struct** %2, align 8
%13 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %12, i64 0
%14 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %13, i32 0, i32 0
store i32 %11, i32* %14, align 4
ret void
}
getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %3, i64 1
%struct.munger_struct
当前索引基本类型
%struct.munger_struct*
当前索引结构体地址
i64 1
当前数组 Index
%5 = getelementptr inbounds %struct.munger_struct, %struct.munger_struct* %4, i32 0, i32 0
第一个 i32 0
结构体指针偏移 0
字节 相当于不偏移
第二个 i32 0
获取第一个结构体成员
总结
- 第⼀个索引不会改变返回指针的类型,也就是说
ptrval
前⾯的*
对应什么类型,就返回什么类型 - 第⼀个索引的偏移量是由第⼀个索引的值和第⼀个
ty
指定的基本类型共同确定的 - 后⾯的索引是在数组或结构体内进⾏索引
- 每增加⼀个索引,就会使该索引使⽤的基本类型和返回指针的类型去掉⼀层
5.2:通过IR分析闭包
了解 IR
语法后我们将以下代码通过 swiftc main.swift -emit-ir > ./main.ll
生成 .ll
文件
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
let makeInc = makeIncrementer()
IR
代码
%swift.function = type { i8*, %swift.refcounted* }
%swift.refcounted = type { %swift.type*, i64 }
%swift.type = type { i64 }
%swift.full_boxmetadata = type { void (%swift.refcounted*)*, i8**, %swift.type, i32, i8* }
%TSi = type <{ i64 }>
···
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"()
%4 = extractvalue { i8*, %swift.refcounted* } %3, 0
%5 = extractvalue { i8*, %swift.refcounted* } %3, 1
store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8
store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8
ret i32 0
}
在 main
函数中
-
%2 = bitcast i8** %1 to i8*
指针类型的转换 -
%3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"()
调用了s4main15makeIncrementerSiycyF
函数。通过LLVM
指令xcrun swift-demangle s4main15makeIncrementerSiycyF
可以得到$s4main15makeIncrementerSiycyF ---> main.makeIncrementer() -> () -> Swift.Int
也就意味着这里调用的是makeIncrementer()
函数 并且返回的是{ i8*, %swift.refcounted* }
结构体类型,有两个元素一个i8*
8位整型的指针,一个%swift.refcounted*
类型的指针 。通过上面的%swift.refcounted = type { %swift.type*, i64 }
和%swift.type = type { i64 }
可以得出%swift.refcounted
就是一个{ i64* , i64*}
的结构体,其实这里的%swift.type = type { i64 }
就是metaData
由此可以还原出
struct ClosureData {
var unkown: UnsafeRawPointer
var refcount: RefCount
}
struct RefCount {
var metaData: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
-
%4 = extractvalue { i8*, %swift.refcounted* } %3, 0
就是取i8*
的值 -
%5 = extractvalue { i8*, %swift.refcounted* } %3, 1
就是取%swift.refcounted*
的值,也就是%5
就是RefCount
结构体的首地址 -
store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 0), align 8
这里的%swift.function
就是%swift.function = type { i8*, %swift.refcounted* }
,通过LLVM
指令xcrun swift-demangle s4main7makeIncSiycvp
可以得到$s4main7makeIncSiycvp ---> main.makeInc : () -> Swift.Int
。那么这句代码的意思就是将%4
存储到makeInc
这个变量{ i8*, %swift.refcounted* }
结构体中第一个元素i8*
-
store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7makeIncSiycvp", i32 0, i32 1), align 8
那么这句代码的意思就是将%5
就是RefCount
结构体的首地址存储到makeInc
这个变量{ i8*, %swift.refcounted* }
结构体中第二个元素%swift.refcounted*
那么这里具体存储的是什么呢?定位到 $s4main15makeIncrementerSiycyF
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"() #0 {
entry:
%runningTotal.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
%1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %runningTotal.debug, align 8
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
store i64 10, i64* %._value, align 8
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #1
call void @swift_release(%swift.refcounted* %1) #1
%6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
ret { i8*, %swift.refcounted* } %6
}
-
{ i8*, %swift.refcounted* } @"$s4main15makeIncrementerSiycyF"()
可以看到$s4main15makeIncrementerSiycyF
这个就是一个包含了一个i8*
的值和swift.refcounted
的指针的结构体。 -
%1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #1
这里的%1
就是堆空间的内存地址 -
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
取出8 x i8
-
%4 = bitcast [8 x i8]* %3 to %TSi*
将8 x i8
转换成TSi
类型,其中TSi
是%TSi = type <{ i64 }>
是一个64
位的结构体 -
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
获取TSi
结构体 (也就是struct Int { i64 }
) 的value
元素 -
store i64 10, i64* %._value, align 8
将10
存储上面取的value
里面 -
%6 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %1, 1
其中insertvalue
是插入的意思,$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA
通过LLVM
指令xcrun swift-demangle s4main15makeIncrementerSiycyF11incrementerL_SiyFTA
可以得到$s4main15makeIncrementerSiycyF11incrementerL_SiyFTA ---> partial apply forwarder for incrementer #1 () -> Swift.Int in main.makeIncrementer() -> () -> Swift.Int
, 也就意味着这里将内嵌函数incrementer()
转换成i8*
(void *
) 插入到结构体{ i8*, %swift.refcounted* }
中的一个元素i8 *
也就是将内嵌函数incrementer()
的函数地址存放到结构体中的一个元素。第二个元素%swift.refcounted* %1, 1
就是将%1
也就是开辟的实例对象的内存空间,并且上面已经将值10
存入到了内存空间当中,所以第二个插入的元素就是一个实例对象内存地址。
5.3:闭包的结构还原
由上述分析可以还原出
struct ClosureData {
var ptr: UnsafeRawPointer
var object: HeapObject
}
struct HeapObject {
var metaData: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
在 SIL
文件中可以看到他是用 Box
进行了一层包裹,那么由此可以还原出
struct ClosureData<Box>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object: HeapObject
var value: T
}
因此闭包的本质就是: 闭包的执行地址 + 捕获变量堆空间的地址
接下来验证一下
func makeIncrementer() -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += 1
return runningTotal
}
return incrementer
}
struct ClosureData<Box>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T>{
var object: HeapObject
var value: T
}
// 结构体是值类型 所以demoStruct 就是 f
struct demoStruct {
var f:() -> Int
}
var f = demoStruct(f: makeIncrementer())
let ptr = UnsafeMutablePointer<demoStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
// 内存属性绑定
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.object)
print("end")
打印结果:
0x0000000100002b10
0x0000000101022d70
end
接着我们在 mach-o
文件中查找一下这个地址 0x0000000100002b10
是什么 通过 LLVM
命令 nm -p mach-o文件地址 | grep 0000000100002b10
开看一下
这里可以看到
0x0000000100002b10
在 mach-o
中是 s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA
这玩意儿,有了上面的经验我们转换一下还是通过 LLVM
命令 xcrun swift-demangle s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA
可以得到s19LJLSwiftClosureDemo15makeIncrementerSiycyF11incrementerL_SiyFTA.png
因此可以得到结论
0x0000000100002b10
就是在函数 makeIncrementer()
中的内嵌函数 incrementer()
在
print("end")
处打个断点打印一下 0x0000000101022d70
0x0000000100002b10.png
5.4:闭包捕获多个值的结构
func makeIncrementer(amount: Int) -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
var a = makeIncrementer(amount: 15)
转换成 IR
代码
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
%3 = call swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementer6amountSiycSi_tF"(i64 15)
%4 = extractvalue { i8*, %swift.refcounted* } %3, 0
%5 = extractvalue { i8*, %swift.refcounted* } %3, 1
store i8* %4, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1aSiycvp", i32 0, i32 0), align 8
store %swift.refcounted* %5, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main1aSiycvp", i32 0, i32 1), align 8
ret i32 0
}
不难得出 $s4main15makeIncrementer6amountSiycSi_tF
就是 makeIncrementer()
函数,定位到 $s4main15makeIncrementer6amountSiycSi_tF
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncrementer6amountSiycSi_tF"(i64 %0) #0 {
entry:
%amount.debug = alloca i64, align 8
%1 = bitcast i64* %amount.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
%runningTotal.debug = alloca %TSi*, align 8
%2 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
store i64 %0, i64* %amount.debug, align 8
%3 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
%4 = bitcast %swift.refcounted* %3 to <{ %swift.refcounted, [8 x i8] }>*
%5 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %4, i32 0, i32 1
%6 = bitcast [8 x i8]* %5 to %TSi*
store %TSi* %6, %TSi** %runningTotal.debug, align 8
%._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
store i64 10, i64* %._value, align 8
%7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2
%8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 32, i64 7) #2
%9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
%10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
store %swift.refcounted* %3, %swift.refcounted** %10, align 8
%11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
%._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
store i64 %0, i64* %._value1, align 8
call void @swift_release(%swift.refcounted* %3) #2
%12 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncrementer6amountSiycSi_tF11incrementerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1
ret { i8*, %swift.refcounted* } %12
}
我们可以看到有两次 @swift_allocObject
的调用,第一次的 @swift_allocObject
与上面分析单个捕获值的闭包时的 IR
代码差不多, 第二次 @ swift_allocObject
可以看到这里有个结构体是 { %swift.refcounted, %swift.refcounted*, %TSi }
并且
-
%10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
取出{ %swift.refcounted, %swift.refcounted*, %TSi }
结构体的第二个元素%swift.refcounted*
-
store %swift.refcounted* %3, %swift.refcounted** %10, align 8
将%3
存储在%10
中,%3
就是第一个@ swift_allocObject
-
%11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
取出结构体的第三个元素%TSi
-
%._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
获取值 -
store i64 %0, i64* %._value1, align 8
将值存到%0
中也就是amount
综上分析可以得到之前的闭包结构中的Box
变成了另外一种结构体
func makeIncrementer(amount: Int) -> () -> Int {
var runningTotal = 10
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
struct ClosureData<Box>{
var ptr: UnsafeRawPointer
var object: UnsafePointer<Box>
}
struct HeapObject {
var matedata: UnsafeRawPointer
var refcount1: Int32
var refcount2: Int32
}
struct Box<T1, T2>{
var object: HeapObject
var value1: UnsafePointer<T1> //第一个变量
var value2: T2 //第二个变量
}
// 结构体是值类型 所以demoStruct 就是 f
struct demoStruct {
var f:() -> Int
}
var f = demoStruct(f: makeIncrementer(amount: 15))
let ptr = UnsafeMutablePointer<demoStruct>.allocate(capacity: 1)
ptr.initialize(to: f)
// 内存属性绑定
let ctx = ptr.withMemoryRebound(to: ClosureData<Box<Int, Int>>.self, capacity: 1) {
$0.pointee
}
print(ctx.ptr)
print(ctx.object.pointee.value1)
print(ctx.object.pointee.value2)
print("end")
打印结果:
0x0000000100002b40
0x0000000100727960
15
六:defer关键字
6.1 defer的概念
defer {}
里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return
的,即使程序抛出错误,也会执行。
如果有多个 defer
语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。
func test() {
defer { print("First defer") }
defer { print("Second defer") }
print("End of function")
}
test()
打印结果:
End of function
Second defer
First defer
6.2 defer的使用
func append(string: String, toFileAt url: URL) throws {
let data = string.data(using: .utf8)!
let fileHandle = try FileHandle(forUpdating: url)
defer {
fileHandle.closeFile()
}
if string.isEmpty {
return
}
guard FileManager.default.fileExists(atPath: url.path) else {
try data.write(to: url)
return
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
}
let url = URL(fileURLWithPath: NSHomeDirectory() + "/Desktop/swift.txt")
try append(string: "Swift", toFileAt: url)
try append(string: "Line 1", toFileAt: url)
try append(string: "Line 2", toFileAt: url)
使用 defer
关键字就不需要在每个判断里面将 fileHandle
进行关闭。
七:逃逸闭包
7.1 逃逸闭包的定义
当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping
来明确闭包是允许逃逸的。
- 当闭包被当作属性存储,导致函数完成时闭包生命周期被延长
class Person {
// 闭包作为属性存储
var completionHandler: ((Int) -> Void)?
func makeIncrementer(_ amount: Int, handler: @escaping ((Int) -> Void)) {
var total = 10
total += amount
// 调用makeIncrementer传入的的闭包(handler) 赋值给属性
// 这时闭包(handler)的生命周期比函数(makeIncrementer)的生命周期长
// 闭包(handler) 可以在任何时候执行 取决于 completionHandler 什么时候调用
self.completionHandler = handler
}
func function() {
self.makeIncrementer(10) {
print($0)
}
}
}
var p = Person()
p.function()
p.completionHandler?(10)
- 当闭包异步执行,导致函数完成时闭包生命周期被延长
class Person {
func makeIncrementer(_ amount: Int, handler: @escaping ((Int) -> Void)) {
var total = 10
total += amount
// 主线程在运行过程中遇到了异步线程,继续执行主线程。这里闭包(handler)在异步线程中执行
// 此时闭包(handler)的生命周期比函数(makeIncrementer)的生命周期长
DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
handler(total)
}
}
func function() {
self.makeIncrementer(10) {
print($0)
}
}
}
- 可选类型的闭包默认是逃逸闭包
class Person {
var completionHandler: ((Int) -> Void)?
func makeIncrementer(_ amount: Int, handler: ((Int) -> Void)?) {
var total = 10
total += amount
completionHandler?(total)
}
}
可选类型的闭包默认是逃逸闭包.png
7.4 逃逸闭包与非逃逸闭包的区别
7.4.1 逃逸闭包
var handler: (() -> Void)?
func testEscaping(_ f: @escaping(() -> Void)) {
handler = f
}
func test() -> Int {
var age = 10
testEscaping {
age += 20
}
return age
}
var a = test()
通过 swiftc main.swift -emit-ir > ./main.ll
将代码编译成 IR
代码并且找到 test()
函数的位置
define hidden swiftcc i64 @"$s4main4testSiyF"() #0 {
entry:
%age.debug = alloca %TSi*, align 8
%0 = bitcast %TSi** %age.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %0, i8 0, i64 8, i1 false)
%access-scratch = alloca [24 x i8], align 8
%1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
%2 = bitcast %swift.refcounted* %1 to <{ %swift.refcounted, [8 x i8] }>*
%3 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %2, i32 0, i32 1
%4 = bitcast [8 x i8]* %3 to %TSi*
store %TSi* %4, %TSi** %age.debug, align 8
%._value = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
store i64 10, i64* %._value, align 8
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %1) #2
call swiftcc void @"$s4main12testEscapingyyyycF"(i8* bitcast (void (%swift.refcounted*)* @"$s4main4testSiyFyycfU_TA" to i8*), %swift.refcounted* %1)
call void @swift_release(%swift.refcounted* %1) #2
%6 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %6)
%7 = bitcast %TSi* %4 to i8*
call void @swift_beginAccess(i8* %7, [24 x i8]* %access-scratch, i64 32, i8* null) #2
%._value1 = getelementptr inbounds %TSi, %TSi* %4, i32 0, i32 0
%8 = load i64, i64* %._value1, align 8
call void @swift_endAccess([24 x i8]* %access-scratch) #2
%9 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %9)
call void @swift_release(%swift.refcounted* %1) #2
ret i64 %8
}
%1 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
我们可以看到这里创建了堆内存空间并且捕获了变量 age
7.4.2 非逃逸闭包
func testNoEscaping(_ f: () -> Void) {
f()
}
func test() -> Int {
var age = 10
testNoEscaping {
age += 20
}
return age
}
var a = test()
print(a)
这里的 testNoEscaping
就是非逃逸闭包我们可以通过 SIL
代码来确定
// testNoEscaping(_:)
sil hidden [ossa] @$s4main14testNoEscapingyyyyXEF : $@convention(thin) (@noescape @callee_guaranteed () -> ()) -> () {
// %0 "f" // users: %2, %1
bb0(%0 : $@noescape @callee_guaranteed () -> ()):
debug_value %0 : $@noescape @callee_guaranteed () -> (), let, name "f", argno 1 // id: %1
%2 = apply %0() : $@noescape @callee_guaranteed () -> ()
%3 = tuple () // user: %4
return %3 : $() // id: %4
} // end sil function '$s4main14testNoEscapingyyyyXEF'
可以看到一个关键字 @noescape
对于非逃逸闭包,再编译成 IR
代码
// test() 函数
define hidden swiftcc i64 @"$s4main4testSiyF"() #0 {
entry:
%0 = alloca %TSi, align 8
%1 = bitcast %TSi* %0 to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
%2 = bitcast %TSi* %0 to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %2)
%._value = getelementptr inbounds %TSi, %TSi* %0, i32 0, i32 0
store i64 10, i64* %._value, align 8
%3 = alloca i8, i64 24, align 16
%4 = bitcast i8* %3 to %swift.opaque*
%5 = bitcast %swift.opaque* %4 to <{ %swift.refcounted, %TSi* }>*
%6 = getelementptr inbounds <{ %swift.refcounted, %TSi* }>, <{ %swift.refcounted, %TSi* }>* %5, i32 0, i32 1
store %TSi* %0, %TSi** %6, align 8
call swiftcc void @"$s4main14testNoEscapingyyyyXEF"(i8* bitcast (void (%swift.refcounted*)* @"$s4main4testSiyFyyXEfU_TA" to i8*), %swift.opaque* %4)
%._value1 = getelementptr inbounds %TSi, %TSi* %0, i32 0, i32 0
%7 = load i64, i64* %._value1, align 8
%8 = bitcast %TSi* %0 to i8*
call void @llvm.lifetime.end.p0i8(i64 8, i8* %8)
ret i64 %7
}
这里可以看到这里没有 swift_allocObject
关键字也就是说这里并不会去开辟内存空间去捕获变量 age
而是直接获取 age
变量的值去使用。
7.4.3 非逃逸闭包的优点
- 不会产生循环引用,函数作用域内释放
- 编译器更多性能优化 (如没有
retain
,relsase
) - 上下文的内存保存在栈上,不是堆上