Published on

Swift源代码解读(一)Bool类型

Authors
  • avatar
    Name
    祝你好运
    Twitter

经过上一篇建立环境之后,我们就可以在Xcode中阅读Bool的源代码了,放一个截图:

Swift language source code bool

Swift的Bool值类型。 先看Bool的定义:

@_fixed_layout
public struct Bool {

这里的@_fixed_layout是在告诉SIL(Swift Intermediate Language)Bool的内存布局是固定的。(这里不是很懂,为什么要固定?后面再补充吧)

然后就是Bool的内部值:

@_versioned
internal var _value: Builtin.Int1

这里的@_versioned不太懂,找到的资料是Add a @_versioned attribute for testing resilience.。只是大概明白是SIL会用到。

然后_value被定义为internal的,按照Swift的访问控制internal级别的实体在定义它的模块内是可以访问到的,但是在定义它的模块之外,是无法访问的。我们也确实是无法在我们的代码中直接访问Bool_value属性。

这里的Builtin.Int1是一个平时不会遇到的类型,通过Xcode自带的跳转找不到它的定义,后面知道了再补充。猜测就是一个字节的整数。

然后就是三个初始化函数:

  @_transparent
  public init() {
    let zero: Int8 = 0
    self._value = Builtin.trunc_Int8_Int1(zero._value)
  }

  @_versioned
  @_transparent
  internal init(_ v: Builtin.Int1) { self._value = v }

  public init(_ value: Bool) {
    self = value
  }

其中两个都是public,一个是不接受参数的init(),默认为false;另一个是接受外部传进来的Bool值的初始化参数init(_ value: Bool),根据外面传进来的参数来初始化自己。然后为什么是self = value?而不是self._value = value._value

接下来是ExpressibleByBooleanLiteral以及它对应的内部协议_ExpressibleByBuiltinBooleanLiteral

  @_transparent
  public init(_builtinBooleanLiteral value: Builtin.Int1) {
    self._value = value
  }

  @_transparent
  public init(booleanLiteral value: Bool) {
    self = value
  }

简单来说就是有了上面这两个方法,就可以直接用BoolBuiltin.Int1的字面常量来初始化一个新的Bool实例。而且这两个方式不希望外部人员调用的,应该用let a = true这样的方式来初始化,这样写就会调用对应的初始化方法。

接下来是一个内部才会使用的方法:

extension Bool {
  // This is a magic entry point known to the compiler.
  @_transparent
  public // COMPILER_INTRINSIC
  func _getBuiltinLogicValue() -> Builtin.Int1 {
    return _value
  }
}

虽然外部也可以调用,但是没什么实际的用途:Implicit public access to (Bool) internal type 'Builtin.Int1': runtime exception if appending instances of 'Int1' to an array

接下来是CustomStringConvertible

extension Bool : CustomStringConvertible {
  /// A textual representation of the Boolean value.
  public var description: String {
    return self ? "true" : "false"
  }
}

有了这个协议我们在打印Bool变量的时候,就可以方便的看到它是true还是false,注意这两个是字符串。更多细节可以看官方文档:CustomStringConvertible

接下来又是一个内部方法:

// This is a magic entry point known to the compiler.
@_transparent
public // COMPILER_INTRINSIC
func _getBool(_ v: Builtin.Int1) -> Bool { return Bool(v) }

然后就是两个比较重要的协议HashableEquatable

extension Bool : Equatable, Hashable {
  /// The hash value for the Boolean value.
  ///
  /// Two values that are equal always have equal hash values.
  ///
  /// - Note: The hash value is not guaranteed to be stable across different
  ///   invocations of the same program. Do not persist the hash value across
  ///   program runs.
  @_transparent
  public var hashValue: Int {
    return self ? 1 : 0
  }

  @_transparent
  public static func == (lhs: Bool, rhs: Bool) -> Bool {
    return Bool(Builtin.cmp_eq_Int1(lhs._value, rhs._value))
  }
}

很有意思的是注释里面说哈希值并不保证稳定,难道说true的时候也会是0?但是看代码其实应该是稳定的才对啊。奇怪。

这里的相等的判断是用的Builtin的方法来直接判断_value是否相等。为啥不这样写Bool(lhs._value == rhs._value)?难道是因为_value无法直接使用==函数?

然后是通过字符串来初始化的方法:

extension Bool : LosslessStringConvertible {
  public init?(_ description: String) {
    if description == "true" {
      self = true
    } else if description == "false" {
      self = false
    } else {
      return nil
    }
  }
}

值得注意的是这个初始化方法返回的是可选类型的,也就是说并不一定能初始化成功。实际使用中这个方法确实是相当于为开发者省了功夫,不用再手工去判断用来初始化的字符串,当然,如果传进来的是TRUE,这个方法还是会跪。真是碰到这种情况,开发者还是得自己写,那为什么标准库不把这个考虑进去?我猜可能并不是所有开发者都想这样做,另一方面统一转成小写也会有效率上面的消耗吧。

最后就是三个操作函数:

  @_transparent
  public static prefix func ! (a: Bool) -> Bool {
    return Bool(Builtin.xor_Int1(a._value, true._value))
  }

  @_transparent
  @inline(__always)
  public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
      -> Bool {
    return lhs ? try rhs() : false
  }

  @_transparent
  @inline(__always)
  public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows
      -> Bool {
    return lhs ? true : try rhs()
  }

然后注意这里的@autoclosure,自动闭包,具体的可以参考这里:@AUTOCLOSURE 和 ??