Swift进阶-23-Swift中Array探究

1,431 阅读3分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

  • 本文主要介绍Swift中数组Array的探究

1. 数组的组成

我们定义一个number的数组,并把它编译成SIL文件

var number = [1,2,3,4,5,6]

print(number)

编译SIL文件

image.png

主要的是关注初始化的数组的时候会调用_allocateUninitializedArray方法,之后存储我们的值。我们可以在源码中全局搜索下这个方法可以得到:

我们就在 ArrayShared.swift 中找到这个函数的具体实现

@inlinable // FIXME(inline-always)
@inline(__always)
@_semantics("array.uninitialized_intrinsic")
public // COMPILER_INTRINSIC
func _allocateUninitializedArray<Element>(_  builtinCount: Builtin.Word)
    -> (Array<Element>, Builtin.RawPointer) {
  let count = Int(builtinCount)
  if count > 0 {
    // Doing the actual buffer allocation outside of the array.uninitialized
    // semantics function enables stack propagation of the buffer.
    let bufferObject = Builtin.allocWithTailElems_1(
      _ContiguousArrayStorage<Element>.self, builtinCount, Element.self)

    let (array, ptr) = Array<Element>._adoptStorage(bufferObject, count: count)
    return (array, ptr._rawValue)
  }
  // For an empty array no buffer allocation is needed.
  let (array, ptr) = Array<Element>._allocateUninitialized(count)
  return (array, ptr._rawValue)
}

可以发现首先判断当前的数组count是否大于0,不大于0则创建一个空的数组。大于0则通过allocWithTailElems_1的方法在堆区创建一个内存空间来存放数组中的元素。之后通过 _adoptStorage 来创建数组,我们来看一下_adoptStorage方法内部是如何实现的, 我们在Array.swift中查找该方法

 @inlinable
  @_semantics("array.uninitialized")
  internal static func _adoptStorage(
    _ storage: __owned _ContiguousArrayStorage<Element>, count: Int
  ) -> (Array, UnsafeMutablePointer<Element>) {

    let innerBuffer = _ContiguousArrayBuffer<Element>(
      count: count,
      storage: storage)

    return (
      Array(
        _buffer: _Buffer(_buffer: innerBuffer, shiftedToStartIndex: 0)),
        innerBuffer.firstElementAddress)
  }

创建一个buffer,用来存储我们的数组中的元素。之后返回一个元组,包含这个bufferbuffer的首地址。因此我们可以发现这个元组会返回我们创建的数组和数组的首元素地址。
是因为在第一个元素的地址之前,应该还有其它的信息。这里先给出结论,如图:

image.png

这里简单的介绍下,数组存储的其实是一个名为_ContiguousArrayStorage类的内存地址,而这个类存储这个存储着 元类型引用计数元素的个数,容量的大小和标志位,以及首个元素的内存地址

我么使用LLDB调试下:

image.png

通过调试也验证了上面我们结论的分布情况。

2. 数组拼接

我们通常时候append进行添加,number.append(7)。那么此时的数组应该要扩容,我们查看源码关于append的实现。

@_semantics("array.append_element")
  public mutating func append(_ newElement: __owned Element) {
    // Separating uniqueness check and capacity check allows hoisting the
    // uniqueness check out of a loop.
    _makeUniqueAndReserveCapacityIfNotUnique()
    let oldCount = _buffer.mutableCount
    _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
    _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
    _endMutation()
  }

我们继续看下_makeUniqueAndReserveCapacityIfNotUnique的实现

  @inlinable
  @_semantics("array.make_mutable")
  internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
    if _slowPath(!_buffer.beginCOWMutation()) {
      _createNewBuffer(bufferIsUnique: false,
                       minimumCapacity: count &+ 1,
                       growForAppend: true)
    }
  }

如果缓冲区的存储是唯一引用的,将缓冲区置于可变状态,然后去创建新的 buffer。我们继续看这个_createNewBuffer(bufferIsUnique:minimumCapacity:growForAppend)的实现,代码如下:

/// Copy the contents of the current buffer to a new unique mutable buffer.
  /// The count of the new buffer is set to `oldCount`, the capacity of the
  /// new buffer is big enough to hold 'oldCount' + 1 elements.
  @inline(never)
  @inlinable // @specializable
  internal mutating func _copyToNewBuffer(oldCount: Int) {
    let newCount = oldCount &+ 1
    var newBuffer = _buffer._forceCreateUniqueMutableBuffer(
      countForNewBuffer: oldCount, minNewCapacity: newCount)
    _buffer._arrayOutOfPlaceUpdate(&newBuffer, oldCount, 0)
  }

在这个方法当中它会去调用 _growArrayCapacity 方法来进行扩容,我们继续来看 _growArrayCapacity 的实现

image.png

每次扩容是原有基础的2倍。对于我们的数组来说,当需要扩容的时候,进行2倍扩容而不是只是扩容我们需要的内存,为了防止下次再次添加时可以不扩容,虽然可能会浪费一定的空间,但是提高了效率。