Swift底层进阶--019:Array源码解析

  • Array使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
  • Array会强制检测元素的类型,如果类型不同则会报错。Swift数组应该遵循像Array<Element>这样的形式,其中Element是这个数组中唯一允许存在的数据类型。
  • 如果创建一个数组,并赋值给一个变量,则创建的集合就是可以修改的。这意味着在创建数组后,可以通过添加、删除、修改的方式改变数组里的元素。
  • 如果将一个数组赋值给常量,数组就不可更改,并且数组的大小和内容都不可以修改。
Array的创建

Array有很多创建⽅式:

通过字面量方式创建一个Int类型数组

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

通过字面量方式创建一个String类型数组

var strArray = ["Hank", "CC", "Cooci", "Cat", "Kody"]

也可以使用Array()初始化方法创建数组,但这种方式必须指明数据类型,否则编译报错

使用Array()方法创建数组

var emptyArray: [Int] = Array()

这种写法,等同于上述方式

var emptyArray: Array<Int> = Array()

当访问数组的元素不存在时,运行时会报错:数组越界(Index out of range

如果想在数组创建时给定初始值,可以使用Array(repeating:, count:)方法

var emptyArray = Array(repeating: 0, count: 10)

得到10个长度的数组,初始值全部为0

分析SIL代码

通过SIL代码分析数组的创建过程

var numbers = [1, 2, 3]

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.numbers : [Swift.Int]           // id: %2
  %3 = global_addr @main.numbers : [Swift.Int] : $*Array<Int> // user: %23
  %4 = integer_literal $Builtin.Word, 3           // user: %6
  // function_ref _allocateUninitializedArray<A>(_:)
  %5 = function_ref @Swift._allocateUninitializedArray<A>(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
  %6 = apply %5<Int>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
  %7 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 0 // user: %23
  %8 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 1 // user: %9
  %9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*Int // users: %12, %19, %14
  %10 = integer_literal $Builtin.Int64, 1         // user: %11
  %11 = struct $Int (%10 : $Builtin.Int64)        // user: %12
  store %11 to %9 : $*Int                         // id: %12
  %13 = integer_literal $Builtin.Word, 1          // user: %14
  %14 = index_addr %9 : $*Int, %13 : $Builtin.Word // user: %17
  %15 = integer_literal $Builtin.Int64, 2         // user: %16
  %16 = struct $Int (%15 : $Builtin.Int64)        // user: %17
  store %16 to %14 : $*Int                        // id: %17
  %18 = integer_literal $Builtin.Word, 2          // user: %19
  %19 = index_addr %9 : $*Int, %18 : $Builtin.Word // user: %22
  %20 = integer_literal $Builtin.Int64, 3         // user: %21
  %21 = struct $Int (%20 : $Builtin.Int64)        // user: %22
  store %21 to %19 : $*Int                        // id: %22
  store %7 to %3 : $*Array<Int>                   // id: %23
  %24 = integer_literal $Builtin.Int32, 0         // user: %25
  %25 = struct $Int32 (%24 : $Builtin.Int32)      // user: %26
  return %25 : $Int32                             // id: %26
} // end sil function 'main'
  • %5:通过_allocateUninitializedArray函数进行初始化
  • %7:从元组中获取第0个元素,拿到一个Array
  • %8:从元组中获取第1个元素,拿到一个地址
  • %9:对%8的地址引用,*Int类型
  • %10、%11:创建字面量1
  • store %11 to %9:将字面量1存储到%9
  • %13:地址偏移
  • %14:给定引用值数组的地址,返回数组中index=1位置的地址
  • 下面以此类推,将剩余元素依次放入到连续的存储空间中
源码分析

Array应该是⼀个值类型,但SIL代码中却出现了_alloc函数的调用,通过源码分析,看一下它在底层到底做了什么

打开ArrayShared.swift文件,找到_allocateUninitializedArray的定义:

@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的条件分支
  • 使用_allocateUninitialized函数分配堆内存,先创建_ContiguousArrayStorage类,并在_ContiguousArrayStorage类的尾部,分配builtinCount元素大小的连续内存空间,存放Element.self
  • 调用Array_adoptStorage函数
  • 返回一个元组类型

_allocateUninitialized函数内部调用了HeapObject.cpp文件中的swift_allocObject函数

HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
                                     size_t requiredSize,
                                     size_t requiredAlignmentMask) {
  CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}

打开Array.swift文件,找到_adoptStorage的定义:

  @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)
  }
  • 通过_ContiguousArrayBuffer函数创建innerBuffer私有变量
  • 返回元组类型
  • Array(_buffer: _Buffer(_buffer: , shiftedToStartIndex: ))Array的实例对象
  • innerBuffer.firstElementAddress:当前元素的首地址

找到_Buffer的定义

@frozen
public struct Array<Element>: _DestructorSafeContainer {
  #if _runtime(_ObjC)
  @usableFromInline
  internal typealias _Buffer = _ArrayBuffer<Element>
  #else
  @usableFromInline
  internal typealias _Buffer = _ContiguousArrayBuffer<Element>
  #endif

  @usableFromInline
  internal var _buffer: _Buffer

  /// Initialization from an existing buffer does not have "array.init"
  /// semantics because the caller may retain an alias to buffer.
  @inlinable
  internal init(_buffer: _Buffer) {
    self._buffer = _buffer
  }
}
  • 如果是和ObjC交互,返回_ArrayBuffer,否则返回_ContiguousArrayBuffer

所以在上面SIL代码中的%8,拿到的就是元素的首地址

打开ContiguousArrayBuffer.swift文件,找到_ContiguousArrayBuffer的定义:

@usableFromInline
@frozen
internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol {

  /// Make a buffer with uninitialized elements.  After using this
  /// method, you must either initialize the `count` elements at the
  /// result's `.firstElementAddress` or set the result's `.count`
  /// to zero.
  @inlinable
  internal init(
    _uninitializedCount uninitializedCount: Int,
    minimumCapacity: Int
  ) {
    let realMinimumCapacity = Swift.max(uninitializedCount, minimumCapacity)
    if realMinimumCapacity == 0 {
      self = _ContiguousArrayBuffer<Element>()
    }
    else {
      _storage = Builtin.allocWithTailElems_1(
         _ContiguousArrayStorage<Element>.self,
         realMinimumCapacity._builtinWordValue, Element.self)

      let storageAddr = UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_storage))
      let endAddr = storageAddr + _swift_stdlib_malloc_size(storageAddr)
      let realCapacity = endAddr.assumingMemoryBound(to: Element.self) - firstElementAddress

      _initStorageHeader(
        count: uninitializedCount, capacity: realCapacity)
    }
  }
  • _ContiguousArrayBuffer也是一个结构体
  • 里面只有一个_storage变量

找到init的定义:

  @inlinable
  internal init(count: Int, storage: _ContiguousArrayStorage<Element>) {
    _storage = storage

    _initStorageHeader(count: count, capacity: count)
  }
  • init方法中,_storage存储的是alloc出来的堆上的内存

找到_storage的定义:

  @usableFromInline
  internal var _storage: __ContiguousArrayStorageBase
  • _storage是一个__ContiguousArrayStorageBase类型变量

打开SwiftNativeNSArray.swift文件,找到__ContiguousArrayStorageBase的定义:

@usableFromInline
@_fixed_layout
internal class __ContiguousArrayStorageBase
  : __SwiftNativeNSArrayWithContiguousStorage {

  @usableFromInline
  final var countAndCapacity: _ArrayBody

  @inlinable
  @nonobjc
  internal init(_doNotCallMeBase: ()) {
    _internalInvariantFailure("creating instance of __ContiguousArrayStorageBase")
  }
  
#if _runtime(_ObjC)
  internal override func withUnsafeBufferOfObjects<R>(
    _ body: (UnsafeBufferPointer<AnyObject>) throws -> R
  ) rethrows -> R {
    if let result = try _withVerbatimBridgedUnsafeBuffer(body) {
      return result
    }
    _internalInvariantFailure(
      "Can't use a buffer of non-verbatim-bridged elements as an NSArray")
  }

  /// If the stored type is bridged verbatim, invoke `body` on an
  /// `UnsafeBufferPointer` to the elements and return the result.
  /// Otherwise, return `nil`.
  internal func _withVerbatimBridgedUnsafeBuffer<R>(
    _ body: (UnsafeBufferPointer<AnyObject>) throws -> R
  ) rethrows -> R? {
    _internalInvariantFailure(
      "Concrete subclasses must implement _withVerbatimBridgedUnsafeBuffer")
  }

  internal func _getNonVerbatimBridgingBuffer() -> _BridgingBuffer {
    _internalInvariantFailure(
      "Concrete subclasses must implement _getNonVerbatimBridgingBuffer")
  }
  
  @objc(mutableCopyWithZone:)
  dynamic internal func mutableCopy(with _: _SwiftNSZone?) -> AnyObject {
    let arr = Array<AnyObject>(_ContiguousArrayBuffer(self))
    return _SwiftNSMutableArray(arr)
  }
  
  @objc(indexOfObjectIdenticalTo:)
  dynamic internal func index(ofObjectIdenticalTo object: AnyObject) -> Int {
    let arr = Array<AnyObject>(_ContiguousArrayBuffer(self))
    return arr.firstIndex { $0 === object } ?? NSNotFound
  }
#endif

@inlinable
  internal func canStoreElements(ofDynamicType _: Any.Type) -> Bool {
    _internalInvariantFailure(
      "Concrete subclasses must implement canStoreElements(ofDynamicType:)")
  }

  /// A type that every element in the array is.
  @inlinable
  internal var staticElementType: Any.Type {
    _internalInvariantFailure(
      "Concrete subclasses must implement staticElementType")
  }
  
  @inlinable
  deinit {
    _internalInvariant(
      self !== _emptyArrayStorage, "Deallocating empty array storage?!")
  }
}
  • __ContiguousArrayStorageBase是一个Class类型
  • 有一个_ArrayBody类型的成员属性countAndCapacity

打开ContiguousArrayBuffer.swift文件,找到_initStorageHeader的定义:

  @inlinable
  internal func _initStorageHeader(count: Int, capacity: Int) {
#if _runtime(_ObjC)
    let verbatim = _isBridgedVerbatimToObjectiveC(Element.self)
#else
    let verbatim = false
#endif

    // We can initialize by assignment because _ArrayBody is a trivial type,
    // i.e. contains no references.
    _storage.countAndCapacity = _ArrayBody(
      count: count,
      capacity: capacity,
      elementTypeIsBridgedVerbatim: verbatim)
  }
  • _initStorageHeader函数内调用_ArrayBody函数,赋值给_storagecountAndCapacity属性

打开ArrayBody.swift文件,找到_ArrayBody的定义:

@frozen
@usableFromInline
internal struct _ArrayBody {
  @usableFromInline
  internal var _storage: _SwiftArrayBodyStorage

  @inlinable
  internal init(
    count: Int, capacity: Int, elementTypeIsBridgedVerbatim: Bool = false
  ) {
    _internalInvariant(count >= 0)
    _internalInvariant(capacity >= 0)
    
    _storage = _SwiftArrayBodyStorage(
      count: count,
      _capacityAndFlags:
        (UInt(truncatingIfNeeded: capacity) &<< 1) |
        (elementTypeIsBridgedVerbatim ? 1 : 0))
  }

  /// In principle ArrayBody shouldn't need to be default
  /// constructed, but since we want to claim all the allocated
  /// capacity after a new buffer is allocated, it's typical to want
  /// to update it immediately after construction.
  @inlinable
  internal init() {
    _storage = _SwiftArrayBodyStorage(count: 0, _capacityAndFlags: 0)
  }
  
  /// The number of elements stored in this Array.
  @inlinable
  internal var count: Int {
    get {
      return _assumeNonNegative(_storage.count)
    }
    set(newCount) {
      _storage.count = newCount
    }
  }

  /// The number of elements that can be stored in this Array without
  /// reallocation.
  @inlinable
  internal var capacity: Int {
    return Int(_capacityAndFlags &>> 1)
  }

  /// Is the Element type bitwise-compatible with some Objective-C
  /// class?  The answer is---in principle---statically-knowable, but
  /// I don't expect to be able to get this information to the
  /// optimizer before 1.0 ships, so we store it in a bit here to
  /// avoid the cost of calls into the runtime that compute the
  /// answer.
  @inlinable
  internal var elementTypeIsBridgedVerbatim: Bool {
    get {
      return (_capacityAndFlags & 0x1) != 0
    }
    set {
      _capacityAndFlags
        = newValue ? _capacityAndFlags | 1 : _capacityAndFlags & ~1
    }
  }

  /// Storage optimization: compresses capacity and
  /// elementTypeIsBridgedVerbatim together.
  @inlinable
  internal var _capacityAndFlags: UInt {
    get {
      return _storage._capacityAndFlags
    }
    set {
      _storage._capacityAndFlags = newValue
    }
  }
}
  • _ArrayBody是一个结构体
  • _ArrayBody结构体中,包含一个_SwiftArrayBodyStorage类型的_storage变量

打开GlobalObjects.h文件,找到_SwiftArrayBodyStorage的定义:

struct _SwiftArrayBodyStorage {
  __swift_intptr_t count;
  __swift_uintptr_t _capacityAndFlags;
};
  • _SwiftArrayBodyStorage也是一个结构体
  • 包含count_capacityAndFlags两个成员属性
Array内存布局

现在梳理一下Array的内存布局:

Struct Array->Struct _ContiguousArrayBuffer->Class __ContiguousArrayStorageBase-> 包含⼀个Struct ArrayBody属性 -> 包含⼀个struct _SwiftArrayBodyStorage属性 -> 包含count_capacityAndFlags两个属性

这⾥⼤致推导出Array的内存布局,其实最主要和__ContiguousArrayStorageBase类有关,因为之前的都是值类型

通过LLDB调试来验证⼀下:

定义numbers数组,初始化123三个元素

var numbers = [1, 2, 3]

通过withUnsafePointer打印numbers内存地址

通过x/8g查看numbers的内存地址,里面包含了一个堆上的地址0x0000000100410830

通过x/8g查看内存地址0x0000000100410830

  • 0x00007fff995404d8:元类型metaData,通过cat address打印,它存储在__DATA.__bss段,即:未初始化段
  • 0x0000000200000002:引用计数refCount
  • 0x0000000000000003:数组大小count,这里输出为3
  • 0x0000000000000006:容量_capacityAndFlags,这里输出为6。这是什么情况?难道容量是数组大小的2倍吗?
  • 后面三位分别是数组内元素123

通过po打印numbers,由于返回的是元素首地址,所以直接输出的是值,而不是Array的内存数据结构

Array的容量(capacity)

numbers数组的大小为3,为什么容量却是6,难道容量是数组大小的2倍

打开ArrayBody.swift文件,在_ArrayBody结构体内,找到对_storage变量进行赋值的代码:

_storage = _SwiftArrayBodyStorage(
      count: count,
      _capacityAndFlags:
        (UInt(truncatingIfNeeded: capacity) &<< 1) |
        (elementTypeIsBridgedVerbatim ? 1 : 0))
  • _capacityAndFlags容量被赋值时,对capacity进行了&运算的操作
  • elementTypeIsBridgedVerbatim用来判断当前元素是否允许桥接ObjC

在上面的案例中,numbers数组的容量应该为3,经过&运算操作输出为6

  • 本质上Array的容量还是3
Array不同创建⽅式的差别

Array不同的创建⽅式,导致Metadata有所差别

使用字面量方式或者通过Array(repeating:, count:)方法创建数组

  • Metadata打印为InitialAllocationPool,存储在__DATA.__bss

使用Array()方法创建数组

  • Metadata打印的类型是_swiftEmptyArrayStorage,存储在__DATA.__data

打开GlobalObjects.h文件,找到_SwiftEmptyArrayStorage的定义:

struct _SwiftEmptyArrayStorage {
  struct HeapObject header;
  struct _SwiftArrayBodyStorage body;
};
  • _SwiftEmptyArrayStorage是一个结构体,里面包含HeapObject结构体和_SwiftArrayBodyStorage结构体
Array的数据的拼接

Array进行append操作时,肯定会涉及到数组的扩容

var numbers = [1, 2, 3, 4, 5, 6]
numbers.append(7)

对于数组的扩容,底层是如何处理的?

打开Array.swift文件,找到append的定义:

  @inlinable
  @_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 = _getCount()
    _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
    _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
  }
  • _makeUniqueAndReserveCapacityIfNotUnique:判断当前Array是否有唯一的引用计数
  • _reserveCapacityAssumingUniqueBuffer:将数组进行反转
  • _appendElementAssumeUniqueAndCapacityappend元素到Array

找到_makeUniqueAndReserveCapacityIfNotUnique的定义:

  @inlinable
  @_semantics("array.make_mutable")
  internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
    if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
      _createNewBuffer(bufferIsUnique: false,
                       minimumCapacity: count + 1,
                       growForAppend: true)
    }
  }
  • isMutableAndUniquelyReferenced:判断是否存在多个引用计数
  • 如果存在多个,调用_createNewBuffer函数触发写时复制

找到_createNewBuffer的定义:

  @_alwaysEmitIntoClient
  @inline(never)
  internal mutating func _createNewBuffer(
    bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
  ) {
    let newCapacity = _growArrayCapacity(oldCapacity: _getCapacity(),
                                         minimumCapacity: minimumCapacity,
                                         growForAppend: growForAppend)
    let count = _getCount()
    _internalInvariant(newCapacity >= count)
    
    let newBuffer = _ContiguousArrayBuffer<Element>(
      _uninitializedCount: count, minimumCapacity: newCapacity)

    if bufferIsUnique {
      _internalInvariant(_buffer.isUniquelyReferenced())

      // As an optimization, if the original buffer is unique, we can just move
      // the elements instead of copying.
      let dest = newBuffer.firstElementAddress
      dest.moveInitialize(from: _buffer.firstElementAddress,
                          count: count)
      _buffer.count = 0
    } else {
      _buffer._copyContents(
        subRange: 0..<count,
        initializing: newBuffer.firstElementAddress)
    }
    _buffer = _Buffer(_buffer: newBuffer, shiftedToStartIndex: 0)
  }
  • _growArrayCapacity:获取到数组扩容后的容量
  • 如果bufferIsUniquetrue,表示引用计数唯一,调用moveInitialize函数,将数组元素移动到新的缓冲区,而不是复制
  • 如果为false,表示存在多个引用计数,调用_copyContents函数,将数组元素复制一份到新的缓冲区

找到_reserveCapacityAssumingUniqueBuffer的定义:

@inlinable
  @_semantics("array.mutate_unknown")
  internal mutating func _reserveCapacityAssumingUniqueBuffer(oldCount: Int) {

    let capacity = _buffer.capacity == 0

    _internalInvariant(capacity ||
                 _buffer.isMutableAndUniquelyReferenced())

    if _slowPath(oldCount + 1 > _buffer.capacity) {
      _createNewBuffer(bufferIsUnique: true,
                       minimumCapacity: oldCount + 1,
                       growForAppend: true)
    }
  }
  • 这个函数的本质为了优化性能
  • count进行+1后,超过数组容量,也会调用_createNewBuffer函数

找到_appendElementAssumeUniqueAndCapacity的定义:

  @inlinable
  @_semantics("array.mutate_unknown")
  internal mutating func _appendElementAssumeUniqueAndCapacity(
    _ oldCount: Int,
    newElement: __owned Element
  ) {
    _internalInvariant(_buffer.isMutableAndUniquelyReferenced())
    _internalInvariant(_buffer.capacity >= _buffer.count + 1)

    _buffer.count = oldCount + 1
    (_buffer.firstElementAddress + oldCount).initialize(to: newElement)
  }
  • oldCount进行+1后,赋值给_buffer.count
  • 通过initialize(to: newElement)将新元素存储到数组中

所以数组的扩容,有两种情况都会创建新的内存空间:

  • 如果数组存在多个引用计数,进行写时复制,创建新内存空间存储元素
  • 如果数组插入新元素后超过数组容量,同样需要创建新内存空间存储元素

打开ArrayShared.swift文件,找到_growArrayCapacity的定义:

@_alwaysEmitIntoClient
internal func _growArrayCapacity(
  oldCapacity: Int, minimumCapacity: Int, growForAppend: Bool
) -> Int {
  if growForAppend {
    if oldCapacity < minimumCapacity {
      // When appending to an array, grow exponentially.
      return Swift.max(minimumCapacity, _growArrayCapacity(oldCapacity))
    }
    return oldCapacity
  }
  // If not for append, just use the specified capacity, ignoring oldCapacity.
  // This means that we "shrink" the buffer in case minimumCapacity is less
  // than oldCapacity.
  return minimumCapacity
}
  • 调用max()判断并返回两个参数中较大的值
  • 传入的参数2,使用_growArrayCapacity(_ capacity:)函数,传入数组当前的容量

找到_growArrayCapacity(_ capacity:)的定义:

@inlinable
internal func _growArrayCapacity(_ capacity: Int) -> Int {
  return capacity * 2
}
  • 数组每次扩容,都会是之前容量的两倍

通过LLDB调试来验证⼀下:

定义numbers数组,初始化123三个元素

var numbers = [1, 2, 3]

通过withUnsafePointer打印numbers内存地址

通过x/8g查看numbers的内存地址

通过x/8g查看内存地址0x000000010070f000

  • 此时numberscount大小为3
  • capacity容量经过&运算显示为6,但本质上还是3

将元素4加入到numbers数组内,上面看到numbers的容量本质上是3,想加入新元素,此时数组必须扩容

numbers.append(4)

append元素后,再通过x/8g查看numbers的内存地址

因为数组的扩容需要创建新的内存空间存储元素,所以打印出堆上的地址发生了改变:

  • append之前:打印地址0x000000010070f000
  • append之后:打印地址0x000000010060f3e0

通过x/8g查看内存地址0x000000010060f3e0

  • 此时numberscount大小为4
  • 按上面源码中的逻辑,数组每次扩容,都会是之前容量的两倍
  • 所以之前numbers容量为3,扩容后应该为6
  • 再经过&运算显示为12,将其转为16进制0xC
Array的赋值

定义numbers数组,初始化123三个元素

var numbers = [1, 2, 3]

numbers赋值给tmp

var tmp = numbers

通过x/8g分别查看numberstmp堆上的地址,此时它们是一样的

tmp数组index=0的元素进行修改

tmp[0] = 2

再通过x/8g分别查看numberstmp堆上的地址,此时tmp发生改变,原因是触发了写时复制

分别po打印numberstmp的值,可以看到numbers并没有受到影响

分析SIL代码
var numbers = [1, 2, 3]
var tmp = numbers
tmp[0] = 2

将上述代码生成SIL文件:swiftc -emit-sil main.swift | xcrun swift-demangle

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.numbers : [Swift.Int]           // id: %2
  %3 = global_addr @main.numbers : [Swift.Int] : $*Array<Int> // users: %23, %26
  %4 = integer_literal $Builtin.Word, 3           // user: %6
  // function_ref _allocateUninitializedArray<A>(_:)
  %5 = function_ref @Swift._allocateUninitializedArray<A>(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
  %6 = apply %5<Int>(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
  %7 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 0 // user: %23
  %8 = tuple_extract %6 : $(Array<Int>, Builtin.RawPointer), 1 // user: %9
  %9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*Int // users: %12, %19, %14
  %10 = integer_literal $Builtin.Int64, 1         // user: %11
  %11 = struct $Int (%10 : $Builtin.Int64)        // user: %12
  store %11 to %9 : $*Int                         // id: %12
  %13 = integer_literal $Builtin.Word, 1          // user: %14
  %14 = index_addr %9 : $*Int, %13 : $Builtin.Word // user: %17
  %15 = integer_literal $Builtin.Int64, 2         // user: %16
  %16 = struct $Int (%15 : $Builtin.Int64)        // user: %17
  store %16 to %14 : $*Int                        // id: %17
  %18 = integer_literal $Builtin.Word, 2          // user: %19
  %19 = index_addr %9 : $*Int, %18 : $Builtin.Word // user: %22
  %20 = integer_literal $Builtin.Int64, 3         // user: %21
  %21 = struct $Int (%20 : $Builtin.Int64)        // user: %22
  store %21 to %19 : $*Int                        // id: %22
  store %7 to %3 : $*Array<Int>                   // id: %23
  alloc_global @main.tmp : [Swift.Int]               // id: %24
  %25 = global_addr @main.tmp : [Swift.Int] : $*Array<Int> // users: %33, %27
  %26 = begin_access [read] [dynamic] %3 : $*Array<Int> // users: %28, %27
  copy_addr %26 to [initialization] %25 : $*Array<Int> // id: %27
  end_access %26 : $*Array<Int>                   // id: %28
  %29 = integer_literal $Builtin.Int64, 0         // user: %30
  %30 = struct $Int (%29 : $Builtin.Int64)        // user: %35
  %31 = integer_literal $Builtin.Int64, 2         // user: %32
  %32 = struct $Int (%31 : $Builtin.Int64)        // user: %37
  %33 = begin_access [modify] [dynamic] %25 : $*Array<Int> // users: %39, %35
  // function_ref Array.subscript.modify
  %34 = function_ref @Swift.Array.subscript.modify : (Swift.Int) -> A : $@yield_once @convention(method) <τ_0_0> (Int, @inout Array<τ_0_0>) -> @yields @inout τ_0_0 // user: %35
  (%35, %36) = begin_apply %34<Int>(%30, %33) : $@yield_once @convention(method) <τ_0_0> (Int, @inout Array<τ_0_0>) -> @yields @inout τ_0_0 // users: %37, %38
  store %32 to %35 : $*Int                        // id: %37
  end_apply %36                                   // id: %38
  end_access %33 : $*Array<Int>                   // id: %39
  %40 = integer_literal $Builtin.Int32, 0         // user: %41
  %41 = struct $Int32 (%40 : $Builtin.Int32)      // user: %42
  return %41 : $Int32                             // id: %42
} // end sil function 'main'
  • 赋值操作:使用copy_addr,将numbers内存里的值赋值给tmp
  • 修改tmp:修改过程中,调用了Arraysubscript属性的modify函数
源码分析

打开Array.swift文件,找到subscript的定义:

  @inlinable
  public subscript(index: Int) -> Element {
    get {
      // This call may be hoisted or eliminated by the optimizer.  If
      // there is an inout violation, this value may be stale so needs to be
      // checked again below.
      let wasNativeTypeChecked = _hoistableIsNativeTypeChecked()

      // Make sure the index is in range and wasNativeTypeChecked is
      // still valid.
      let token = _checkSubscript(
        index, wasNativeTypeChecked: wasNativeTypeChecked)

      return _getElement(
        index, wasNativeTypeChecked: wasNativeTypeChecked,
        matchingSubscriptCheck: token)
    }
    _modify {
      _makeMutableAndUnique() // makes the array native, too
      _checkSubscript_native(index)
      let address = _buffer.subscriptBaseAddress + index
      yield &address.pointee
    }
  }
  • subscript函数内,分别有get函数和_modify函数
  • _modify函数内调用_makeMutableAndUnique函数

找到_makeMutableAndUnique的定义:

  @inlinable
  @_semantics("array.make_mutable")
  internal mutating func _makeMutableAndUnique() {
    if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
      _createNewBuffer(bufferIsUnique: false, minimumCapacity: count,
                       growForAppend: false)
    }
  }
  • 判断是否存在多个引用计数
  • 如果存在多个,调用_createNewBuffer函数
  • _createNewBuffer函数,重新开辟了内存空间,此时修改tmp的值和numbers的值没有任何关联
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容