"Questing for Swift Source Code" 系列是我学习 Swift 源码的心得和记录,内容主要是 Swift 源代码的相关分析和探究,如果您对 Swift 源代码也很感兴趣的话,欢迎阅读这个系列的文章。
Swift 源码极其庞大,里面所使用的语言囊括了 Swift、 Python、C++等,因此我觉得这是一个巨大的坑,我不知道能不能把它填完,不过我会尽力而为的~^_^
我们将一起通过分析和学习 Swift 源码,来体会 Swift 这个最前沿编程语言的设计思想,并且从中找出一些鲜为人知的用例!
Swift 的基本类型包含了 “布尔类型 (boolean)”,表示为 Bool。布尔值通常情况下是作为逻辑值来使用的,因为它的值要么为真 (true),要么为假 (false),除此之外就没有其他值了。
定义
首先我们可以看到 Swift 中对 Bool 的定义:
public struct Bool
在 Swift 中,这些基本类型大多都是由结构体 (struct) 定义的,这也准确地表述了基本类型的“值类型 (value-type)”特征。
随后我们可以看到 Bool 中唯一的实际值:
internal var _value: Builtin.Int1
Builtin 是 Swift 内置库之一,由 C++ 写成,位于 Builtins.cpp 文件当中。我们暂时不去了解 Builtin 的具体调用方式,在之后的文章中我们会进行探讨。只需知道,这个操作会调用 Builtin 会调用此文件中的 swift::getBuiltinType(ASTContext &Context, StringRef Name) 函数,这个函数将会给 LLVM 返回一个用以数据处理的类型。Name 参数就是要获取的类型名称。具体该函数的实现和使用我们这里也暂时不介绍。
在这个函数中,有一个非常重要的片段:
// Handle 'int8' and friends. if (Name.substr(0, 3) == "Int") { unsigned BitWidth; if (!Name.substr(3).getAsInteger(10, BitWidth) && BitWidth <= 2048 && BitWidth != 0) // Cap to prevent insane things. return BuiltinIntegerType::get(BitWidth, Context); }
这个函数将会截取 Name 参数的前 3 个字符,这里也就是截取 Int1 的前 3 个字符,从而判断其是不是 "Int" 一族。随后,开始读取 "Int" 之后的内容,这里用到了定义在 StringRef 中的函数 getAsInteger(unsigned Radix, T &Result)。
getAsInteger(unsigned Radix, T &Result) 函数将当前字符串解析为基于指定基数 (radix) 的整数,例如,Radix 指定为 10 表明将以十进制来解析字符串,Radix 指定为 2 将以二进制来解析字符串。如果字符串无法解析的话,那么这个函数就会返回 true。如果字符串成功解析,那么结果就会写到 Result 当中。
这里用预定义的 BitWidth 来存储转换过的数字,通过这个函数的调用,我们将能得到所读取到的数字 1。随后,要判断读取的数字是否在可处理范围之内(也就是(0,2048])。如果成功的话,就返回一个内置的 BuiltinIntegerType 类型,其定义为:
class BuiltinIntegerType : public BuiltinType
这个内置的整数类型会直接与 LLVM 编译器的 IR 整数类型响应。当然,这其中最重要的值是 BuiltinIntegerWidth 类型的 Width。
BuiltinIntegerWidth Width;
这也说明了,对于 Swift 中内置的整数类型来说,无论是 Int8 还是 Int32,都是属于同样的类型,只不过它们的 Width 属性,也就是“位长(占用空间)”不同而已。
因此,Builtin.Int1 就很好理解了:这代表了一个内置的整数类型,它的位长是 1 个字节,也就是只有 “0” 和 “1” 两个值,刚好符合传统上布尔值的定义:0 代表 false,1 代表 true。
初始化
我们下面来查看定义的布尔值初始化方法。
Swift 内部定义了两个初始化方法:
@_transparent public init() { let zero: Int8 = 0 self._value = Builtin.trunc_Int8_Int1(zero._value) } @_transparent internal init(_ v: Builtin.Int1) { self._value = v }
设置为 public 的只有一个,也就是我们使用的 let bool = Bool(),这个初始化方法的默认值是 false。
解析:从语义上来看,@_transparent 类似于“将此操作视为一种原始操作(primitive operation)”。该特性会导致编译器在管道(pipeline)中更早地将函数内联。
这两个初始化很简单,第一个是将布尔值初始化为 false 的 init() 方法。它建立了一个值为 0 的 Int8 常量(因为 Swift 内部不支持 Int1 的直接建立),然后通过内置的转换函数,将 Builtin.Int8 转换为 Builtin.Int1。
BooleanLiteral 协议
这里有两个协议,是布尔值的字面量协议扩展,实现这两个协议之后就可以使用 true 和 false 字面量或者 1 和 0 字面量直接赋值了。
注意:只有使用 _BuiltinBooleanLiteralConvertible 协议的方法才能使用 1 和 0 字面量进行赋值的,这里的 1 和 0 字面量都会被自动转换位 Builtin.Int1 类型。
_BuiltinBooleanLiteralConvertible 需要实现的是 public init(_builtinBooleanLiteral value: Builtin.Int1) 方法,BooleanLiteralConvertible 需要实现的是 public init(booleanLiteral value: Bool) 方法,这两个方法都被标注为了 @_transparent 作为原始操作。
解析:在目前 Swift 的规则当中,前面加上"_"(下划线)的协议、方法、变量等等都是只在内部有效的,也就是说它们需要设定为 internal 的权限。
我们可以在外面实现 BooleanLiteralConvertible 协议来实现此功能,您可以打开 Playground,写入以下代码来体验一下:
struct TestBoolean: BooleanLiteralConvertible { var value: Int = 0 init() { } init(booleanLiteral value: Bool) { self.value = value ? 1 : 0 } } var test: TestBoolean = true test.value
这样您就可以看到,我们可以用布尔字面量给我们自定义的 TestBoolean 结构体赋值了。
BooleanType 协议
这个协议主要需要实现一个 boolValue 的只读属性,其类型是 Bool。
在 Swift 源码的实现中,除了这个只读属性外,它还实现了两个方法:
@_transparent @warn_unused_result public func _getBuiltinLogicValue() -> Builtin.Int1 { return _value } /// Construct an instance representing the same logical value as /// `value`. public init(_ value: T) { self = value.boolValue }
第一个方法是用以获取内置的实际值的,这是一个内部方法,它返回一个 Builtin.Int1 值。
解析:@warn_unused_result 是一个 GCC 内核中自带的关键字,这个关键字用于检查这个函数被调用的时候是否使用了其返回值,否则的话编译器会对其警告。换句话说,如果只是单纯的调用了此函数,并没有利用其返回值的话(比如说继续判断、赋值等等),那么编译器会弹出警告。
比如,如果上面的函数这样调用的话:true._getBuiltinLogicValue(),那么编译其就会弹出警告,除非使用 let value = true._getBuiltinLogicValue()。
第二个方法就比较有意思了,它可以根据一个同样实现 BooleanType 协议的值来实现自身的初始化。
我们继续使用上面创造的那个 TestBoolean 结构体,在原来的基础上加上:
extension TestBoolean: BooleanType { var boolValue: Bool { return self.value == 1 } }
这样你就可以可以使用下列语句创建一个新的布尔值了:
var bool: Bool = Bool(test) // 输出值为 true
CustomStringConvertible 协议
这是一个 Swift 中绝大多数类型都实现的协议,我们将在探寻 CompilerProtocols 文件时统一进行介绍。
布尔值实现此协议后,你就会发现它能够显示出其字面量的字符串,是 "true" 还是 "false"。
Equatable、Hashable 协议
这是一个 Swift 中大多数类型都实现的协议,我们将在探寻 CompilerProtocols 文件时统一进行介绍。
布尔值实现此协议后,你就会发现它可以使用 == 来判断两个布尔值是否相等。
除此之外,布尔值源文件中还定义了两个方法:
第一个是一个根据 Builtin.Int1 获取布尔值的全局函数:
// This is a magic entry point known to the compiler. @_transparent public // COMPILER_INTRINSIC func _getBool(v: Builtin.Int1) -> Bool
第二个是一个取相反布尔值的运算符函数:
// Unary logical complement. @_transparent @warn_unused_result public prefix func !(a: Bool) -> Bool
这样我们就可以通过 ! 运算符来实现取布尔值的相反值了。
总结
在本文当中,我们介绍了以下几点内容:
Swift 中布尔值的基本定义
Builtin 的介绍
swift::getBuiltinType(ASTContext &Context, StringRef Name) 函数的部分内容
getAsInteger(unsigned Radix, T &Result) 函数作用
@_transparent 和 @warn_unused_result 关键字
BooleanLiteral 协议
BooleanType 协议
至此,这就是我们所看到的 Swift 布尔值的自举部分内容了,这是一个非常简单的类型,但是我们在其中也能看到很多精彩的内容。