前言
本文主要记录研究的结果,暂时没具体说明研究的过程。
简介
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
Swift中的闭包对应Objective-C语言中Block,本文基于对Block底层的理解,来探讨swift闭包实现底层。
代码层
先从代码的层面,对比Block和swift闭包的不同点,来理解swift闭包。
语法
代码例子
objc
int c = 3;
int (^sum)(int, int) = ^(int a, int b) {
return a + b + c;
};
int r = sum(1, 2); // r = 6 a = 1, b = 2, c = 3
swift
var c = 3;
let sum = {(_ a: Int, _ b: Int) -> Int in
return a + b + c;
}
let r = sum(1, 2); // r = 6 a = 1, b = 2, c = 3
小结
从上述最基本的代码可以看到两种语言闭包写法上的不同,大致如下
objc | swift | |
---|---|---|
语法 | ^ 返回值类型 (参数列表) {表达式} | { (参数列表) -> 返回值类型 in 表达式 } |
变量捕获
代码例子
objc
int c = 3; // 拷贝
__block int d = 4; // 引用
int e = 5; // 拷贝
int (^sum)(int, int) = ^(int a, int b) {
// c += 1; // 不支持修改
d += 1;
return a + b + c + d + e;
};
e += 1; // 不影响sum执行的结果
int r1 = sum(1, 2); // r1 = 16 a = 1, b = 2, c = 3, d = 5, e = 5
int r2 = sum(1, 2); // r1 = 17 a = 1, b = 2, c = 3, d = 6, e = 5
swift
var c = 3; // 拷贝
var d = 4; // 引用
var e = 5; // 引用
let sum = {(_ a: Int, _ b: Int) -> Int in
// c += 1; // 支持修改,但捕获类型会变成引用
d += 1;
return a + b + c + d + e;
}
e += 1;
let r1 = sum(1, 2); // r1 = 17 a = 1, b = 2, c = 3, d = 5, e = 6
let r2 = sum(1, 2); // r1 = 18 a = 1, b = 2, c = 3, d = 6, e = 6
小结
从上述代码的运行结果,可以得到以下结论
objc | swift | |
---|---|---|
拷贝 | 只读,不允许修改变量 | 只读,不对变量进行修改 |
引用 | 使用关键字 __block 修饰 | 修改变量,由编译器推断 |
底层实现
接下来,从底层结构代码来看Block和swift闭包。
Objc
数据结构
Block
// Block在内存中的数据结构
typedef void(*BlockInvokeFunction)(void *, ...);
struct Block_layout {
void * isa; // 同类对象的isa指针,但不用于引用计数等
int32_t flags; // 包含引用计数等信息
int32_t reserved;
BlockInvokeFunction invoke; // 函数地址指针
struct Block_descriptor *descriptor; // Block_layout的描述数据,大小不固定,根据flags的标识位判断
// 捕获的变量
};
struct Block_descriptor {
uintptr_t reserved;
uintptr_t size; // Block_layout的具体大小
/*
typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);
struct Block_descriptor_2 {
//requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy; // 复制函数
BlockDisposeFunction dispose; // 析构函数
};
*/
/*
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout;
};
*/
};
引用类型
struct Block_byref {
void *isa; // null
struct Block_byref *forwarding; // 指向最终的数据
volatile int32_t flags; // contains ref count
uint32_t size;
/*
typedef void(*BlockByrefKeepFunction)(struct Block_byref*, struct Block_byref*);
typedef void(*BlockByrefDestroyFunction)(struct Block_byref *);
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
*/
/*
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
*/
// 引用数据
};
函数签名
returntype (*BlockInvokeFunction)(struct Block_layout *, ... /*param list*/)
内存分布例子
swift
数据结构
Swift闭包
// swift闭包的结构体
// 目前没在源码中找到对应的声明
// 下面的swift.function是通过分析IR代码得出
struct swift.function {
void *function; // 函数地址指针
HeapObject *obj; // 捕获的变量,
}
struct HeapObject {
HeapMetadata const *metadata; // 闭包对应是TargetHeapLocalVariableMetadata
InlineRefCounts refCounts; // 包含引用计数等信息
// 捕获的变量
}
// 闭包的Metadata结构体
// 函数签名的描述
struct TargetFunctionTypeMetadata {
StoredPointer Kind;
TargetFunctionTypeFlags<StoredSize> Flags;
ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;
// TargetMetadata *[] // Parameters
// ParameterFlags[]
}
PS: HeapObject是swift用存储堆对象的结构,并负责引用计数等相关的功能,类似与Objc上的class对象,HeapObject覆盖的范围更广,支持class、struct、enum等类型。
引用类型
// 引用类型出差
struct HeapObject {
HeapMetadata const *metadata; // 闭包对应是TargetHeapLocalVariableMetadata
InlineRefCounts refCounts; // 包含引用计数等信息
// 引用数据
}
// 用于存储闭包捕获的变量和引用类型
// 只有destroy函数,没有witness table
struct TargetHeapLocalVariableMetadata {
uintptr_t Kind; // 区分类型,从TargetMetadata继承
uint32_t OffsetToFirstCapture; // 存储数据的起始位置
const void *CaptureDescription;
};
函数签名
returntype SwiftInvokeFunction(... /*param list*/, struct HeapObject *)