窥探Swift源码下的Array

窥探Swift源码下的Array_第1张图片

 窥探Swift源码下的Array_第2张图片 

本文字数:6730

预计阅读时间:15 分钟

用最通俗的语言,描述最难懂的技术

前情提要

我在之前的文章一次遍历导致的崩溃中提到了,如果有机会会把相关的Swift集合源码阅读。

首先对自己的开发和知识储备会有进一步的升华,另外也可以洞察苹果是否有其他的操作等,值得借鉴

前几天的时候也在自己的项目中发现了一些关于Array的崩溃,日志如下

#0 Thread

NSGenericException

*** Collection <__NSArrayM: 0x2836a4120> was mutated while being enumerated.
...

很显然,一个可变数组在遍历的同时对数组进行了修改,因为Array是线程不安全的,所以框架就警告我们不允许这么做,直接抛崩溃处理

问题很简单,但是我考虑的是目前的Apple是如何实现的Array,如果想知道之前的实现可以看《NSMutableArray原理揭露》

最近恰巧在学习和使用相关的Swift的一些框架,趁着周末搬完砖,就开始了源码之旅,我们立刻出发

笔者的相关准备

  • 编译好的Swift 5.5源码

  • C++基础

⚠️:以下源码都在顶部标注了文件以及出现的行数

Array是什么

ArraySwift下数组的实现,了解Swfit的都知道,Swift下的大多数的对象均是由struct组成的,我们找到源码中的Array的定义

// File: Array.swift, line: 299

@frozen public struct Array : Swift._DestructorSafeContainer {
  
  // ...
}

所以ArraySwift下本质就是Struct

Array有什么用

有序的存储一组数据

Array的底层原理

新建一个项目

Xcode->File->New->Project->MacOS->Command Line Tool-Language(Swift) & Product Name

输入以下代码

var num: Array = [1, 2, 3]
withUnsafePointer(to: &num) {
    print($0)
}
print("end")

并在print("end")处打断点

窥探Swift源码下的Array_第3张图片

x/8gLLDB(Low Level Debugger)下的调试命令,作用是查看内存地址里的值

从图中可以看到,并没有找到1,2,3的信息,内存里面只有0x0000000101046c70,猜测是内存某块区域上的地址,所以现在的疑问也有了

  • Array保存的地址是什么?

  • Array保存的数据去哪了?

  • Array的写复制如何实现的?

带着这三个疑问我们继续往下探索...

生成ArraySIL文件

首先我刚才代码文件修改为如下所示,最简单的初始化,有利于我们阅读SIL文件

var num: Array = [1, 2, 3]

在终端使用命令swiftc -emit-sil main.swift | xcrun swift-demangle > ./main.sil生成SIL文件

// 全局标识以@开头,全局变量
// 局部标识以%开头,局部变量
// store写入内存
// load读取内存
// 具体语法请参考:https://llvm.org/docs/LangRef.html(搜索Intermediate Representation)


sil_stage canonical
// 系统内部导入的相关需要的动态库
import Builtin
import Swift
import SwiftShims

import Foundation
// 对一个存放Int可变数组进行setter和getter的声明
@_hasStorage @_hasInitialValue var num: Array { get set }

// num
sil_global hidden @main.num : [Swift.Int] : $Array

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
  alloc_global @main.num : [Swift.Int]               // id: %2,在堆上开辟一个空间
  %3 = global_addr @main.num : [Swift.Int] : $*Array // user: %25,创建临时变量%3存放数组首地址
  %4 = integer_literal $Builtin.Word, 3           // user: %6
  // 初始化数组调用的入口方法,function_ref _allocateUninitializedArray(_:)
  %5 = function_ref @Swift._allocateUninitializedArray(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %6
  %6 = apply %5(%4) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %8, %7
  %7 = tuple_extract %6 : $(Array, Builtin.RawPointer), 0 // user: %24
  %8 = tuple_extract %6 : $(Array, Builtin.RawPointer), 1 // user: %9
  %9 = pointer_to_address %8 : $Builtin.RawPointer to [strict] $*Int // users: %12
  %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
  // function_ref _finalizeUninitializedArray(_:)
  %23 = function_ref @Swift._finalizeUninitializedArray(__owned [A]) -> [A] : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %24
  %24 = apply %23(%7) : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %25
  store %24 to %3 : $*Array                  // id: %25
  %26 = integer_literal $Builtin.Int32, 0         // user: %27
  %27 = struct $Int32 (%26 : $Builtin.Int32)      // user: %28
  return %27 : $Int32                             // id: %28
} // end sil function 'main'

// _allocateUninitializedArray(_:)
sil [always_inline] [_semantics "array.uninitialized_intrinsic"] @Swift._allocateUninitializedArray(Builtin.Word) -> ([A], Builtin.RawPointer) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer)

// Int.init(_builtinIntegerLiteral:)
sil public_external [transparent] @Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int {
// %0                                             // user: %2
bb0(%0 : $Builtin.IntLiteral, %1 : $@thin Int.Type):
  %2 = builtin "s_to_s_checked_trunc_IntLiteral_Int64"(%0 : $Builtin.IntLiteral) : $(Builtin.Int64, Builtin.Int1) // user: %3
  %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5
  return %4 : $Int                                // id: %5
} // end sil function 'Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int'

// _finalizeUninitializedArray(_:)
sil shared_external [readnone] [_semantics "array.finalize_intrinsic"] @Swift._finalizeUninitializedArray(__owned [A]) -> [A] : $@convention(thin)  (@owned Array) -> @owned Array {
// %0                                             // user: %2
bb0(%0 : $Array):
  %1 = alloc_stack $Array                // users: %6, %5, %4, %2
  store %0 to %1 : $*Array               // id: %2
  // function_ref Array._endMutation()
  %3 = function_ref @Swift.Array._endMutation() -> () : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> () // user: %4
  %4 = apply %3(%1) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> ()
  %5 = load %1 : $*Array                 // user: %7
  dealloc_stack %1 : $*Array             // id: %6
  return %5 : $Array                     // id: %7
} // end sil function 'Swift._finalizeUninitializedArray(__owned [A]) -> [A]'

// Array._endMutation()
sil shared_external [_semantics "array.end_mutation"] @Swift.Array._endMutation() -> () : $@convention(method)  (@inout Array) -> () {
// %0                                             // users: %9, %1
bb0(%0 : $*Array):
  %1 = struct_element_addr %0 : $*Array, #Array._buffer // user: %2
  %2 = struct_element_addr %1 : $*_ArrayBuffer, #_ArrayBuffer._storage // user: %3
  %3 = struct_element_addr %2 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue // user: %4
  %4 = load %3 : $*Builtin.BridgeObject           // user: %5
  %5 = end_cow_mutation %4 : $Builtin.BridgeObject // user: %6
  %6 = struct $_BridgeStorage<__ContiguousArrayStorageBase> (%5 : $Builtin.BridgeObject) // user: %7
  %7 = struct $_ArrayBuffer (%6 : $_BridgeStorage<__ContiguousArrayStorageBase>) // user: %8
  %8 = struct $Array (%7 : $_ArrayBuffer) // user: %9
  store %8 to %0 : $*Array               // id: %9
  %10 = tuple ()                                  // user: %11
  return %10 : $()                                // id: %11
} // end sil function 'Swift.Array._endMutation() -> ()'



// Mappings from '#fileID' to '#filePath':
//   'main/main.swift' => 'main.swift'

SIL文件可以看出num的生成调用了@Swift._allocateUninitializedArray()的方法,该方法的返回值是一个元祖%6,然后用%7%8把元祖中的值提取出来,%7给了%3,也就是num的位置了,所以我们刚才断点拿到的0x0000000101046c70就是%7的值了

Array保存的数据依次保存到了%9,%9又是%8的地址的指向 ,所以%7%8是什么?

Array在源码中的定义

因为在SIL文件找不到答案,那么我们就去源码找

// File: Array.swift, line: 299

@frozen
public struct Array: _DestructorSafeContainer {
  #if _runtime(_ObjC)
  @usableFromInline
  internal typealias _Buffer = _ArrayBuffer
  #else
  @usableFromInline
  internal typealias _Buffer = _ContiguousArrayBuffer
  #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
  }
}

我们可以看到Array中只有一个属性_buffer_buffer_runtime(Objc)下是_ArrayBuffer,否则就是_ContiguousArrayBuffer;在苹果设备下应该都是兼容Objc,所以这里应该是_ArrayBuffer

_allocateUninitializedArray

在源码中搜索这个方法,看到下面的实现

// File: ArrayShared.swift, line: 34

/// Returns an Array of `_count` uninitialized elements using the
/// given `storage`, and a pointer to uninitialized memory for the
/// first element.
///
/// This function is referenced by the compiler to allocate array literals.
///
/// - Precondition: `storage` is `_ContiguousArrayStorage`.
@inlinable // FIXME(inline-always)
@inline(__always)
@_semantics("array.uninitialized_intrinsic")
public // COMPILER_INTRINSIC
func _allocateUninitializedArray(_  builtinCount: Builtin.Word)
    -> (Array, 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.self, builtinCount, Element.self)

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

这里可以看到判断count是否大于0,走的不同的方法,但是返回值都是一样的,我们只看其中一个,因为例子中的count是3,所以直接看大于0的分支

首先看到调用到了allocWithTailElems_1方法,调用对象是Builtin,继续走断点调试,进入了swift_allocObject方法

// File: HeapObject.cpp, line: 133

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

这个方法的作用是向堆申请分配一块内存空间,这也是上文中提到的猜测是内存某块区域的一个验证,断点显示requiredSize是56,po指针metadata显示的是_TtGCs23_ContiguousArrayStorageSi_$,这样可以得出allocWithTailElems_1向堆申请分配一块空间,申请的对象类型是_ContiguousArrayStorage

申请好空间之后,继续调用了_adoptStorage方法

// File: Array.swift, line: 947

/// Returns an Array of `count` uninitialized elements using the
/// given `storage`, and a pointer to uninitialized memory for the
/// first element.
///
/// - Precondition: `storage is _ContiguousArrayStorage`.
@inlinable
@_semantics("array.uninitialized")
internal static func _adoptStorage(
  _ storage: __owned _ContiguousArrayStorage, count: Int
) -> (Array, UnsafeMutablePointer) {

  let innerBuffer = _ContiguousArrayBuffer(
    count: count,
    storage: storage)

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

可以看出_adoptStorage的返回值都是跟innerBuffer相关,返回的是一个元祖,元祖里的内容分别对应了innerBuffer的什么呢?

innerBuffer_ContiguousArrayBuffer初始化方法生成的,看下_ContiguousArrayBuffer定义

// File: ContiguousArrayBuffer.swift, line: 289


/// Initialize using the given uninitialized `storage`.
/// The storage is assumed to be uninitialized. The returned buffer has the
/// body part of the storage initialized, but not the elements.
///
/// - Warning: The result has uninitialized elements.
/// 
/// - Warning: storage may have been stack-allocated, so it's
///   crucial not to call, e.g., `malloc_size` on it.
@inlinable
internal init(count: Int, storage: _ContiguousArrayStorage) {
  _storage = storage

  _initStorageHeader(count: count, capacity: count)
}

@inlinable
internal init(_ storage: __ContiguousArrayStorageBase) {
  _storage = storage
}

/// Initialize the body part of our storage.
///
/// - Warning: does not initialize elements
@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)
}

_ContiguousArrayBuffer只有一个属性_storage,初始化方法init(count: Int, storage: _ContiguousArrayStorage)中传进来的storage_ContiguousArrayStorage__ContiguousArrayStorageBase_ContiguousArrayStorage的父类

_ContiguousArrayStorage

_ContiguousArrayStorageclass,查看源码_ContiguousArrayStorage的继承链,发现只有在__ContiguousArrayStorageBase有一个属性

// File: SwiftNativeNSArray.swift, line: 452


@usableFromInline
final var countAndCapacity: _ArrayBody

_ArrayBody是一个结构体,只有一个属性_storage

// File: ArrayBody.swift, line: 20

@frozen
@usableFromInline
internal struct _ArrayBody {
  @usableFromInline
  internal var _storage: _SwiftArrayBodyStorage
    
 // ...   
}

那么_SwiftArrayBodyStorage又是什么?

struct _SwiftArrayBodyStorage {
  __swift_intptr_t count;
  __swift_uintptr_t _capacityAndFlags;
};

很显然_SwiftArrayBodyStorage仍然是结构体,count_capacityAndFlags都是Swift中的指针大小,都是8个字节

_capacityAndFlags

我们继续回到_ContiguousArrayBuffer初始化方法,当_storage赋值完以后就调用了_initStorageHeader(count: count, capacity: count),看函数名是在初始化countcapacity,在_initStorageHeader方法中看到核心内容

_storage.countAndCapacity = _ArrayBody(
      count: count,
      capacity: capacity,
      elementTypeIsBridgedVerbatim: verbatim)

其实_initStorageHeader方法就是给_storage中的countAndCapacity属性赋值

接下来看_ArrayBody是如何初始化的

// File: ArrayBody.swift, line: 26


@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))
}

我们可以看到count就是直接传递赋值了,而capacity(就是属性_capacityAndFlags)在内存中并不是直接存储的,而是向左1位偏移,然后在多出来的1位数据记录了一个elementTypeIsBridgedVerbatim的标识

所以如果在内存中读取capacity的时候,也需要位移操作,这个在_ArrayBody也有体现

// File: ArrayBody.swift, line: 62

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

_ArrayBuffer

到这里innerBuffer的初始化就读完了,回到主干返回值的生成

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

Array(_buffer:)是结构体默认的初始化方法,_Buffer上面也提到了是_ArrayBuffer,现在把_ArrayBuffer的初始化方法结合起来看

// File:ArrayBuffer.swift, line: 25

@usableFromInline
internal typealias _ArrayBridgeStorage
= _BridgeStorage<__ContiguousArrayStorageBase>

@usableFromInline
@frozen
internal struct _ArrayBuffer: _ArrayBufferProtocol {
// ...
@usableFromInline
internal var _storage: _ArrayBridgeStorage
}

extension _ArrayBuffer {
/// Adopt the storage of `source`.
@inlinable
internal init(_buffer source: NativeBuffer, shiftedToStartIndex: Int) {
  _internalInvariant(shiftedToStartIndex == 0, "shiftedToStartIndex must be 0")
  _storage = _ArrayBridgeStorage(native: source._storage)
}

/// `true`, if the array is native and does not need a deferred type check.
@inlinable
internal var arrayPropertyIsNativeTypeChecked: Bool {
  return _isNativeTypeChecked
}


// File: BridgeStorage.swift, line: 57
@frozen
@usableFromInline
internal struct _BridgeStorage {
  @inlinable
  @inline(__always)
  internal init(objC: ObjC) {
    _internalInvariant(_usesNativeSwiftReferenceCounting(NativeClass.self))
    rawValue = _makeObjCBridgeObject(objC)
  }
}

从代码看就是结构体只有一个属性,然后就是属性赋值操作,shiftedToStartIndex传入的0对我们的查找需求也没有影响就是一个简单判断

所以总结就是%7就是_ArrayBuffer的结构体,里面的属性存放了_ContiguousArrayStorage的实例类对象

firstElementAddress

现在继续查SIL文件下的%8,也就是innerBuffer.firstElementAddress

// File: ContigyousArrayBuffer.swift


/// A pointer to the first element.
@inlinable
internal var firstElementAddress: UnsafeMutablePointer {
  return UnsafeMutablePointer(Builtin.projectTailElems(_storage,
                                                       Element.self))
}

Builtin是编译器内置命令的调用,不好查看,我们看下注释

// File: Builtins.def, line: 465

/// projectTailElems :  (C) -> Builtin.RawPointer
///
/// Projects the first tail-allocated element of type E from a class C.
BUILTIN_SIL_OPERATION(ProjectTailElems, "projectTailElems", Special)

所以projectTailElems方法的作用是返回_storage分配空间的尾部元素第一个地址,这样推测下来,数组的元素的存储位置就在_ContiguousArrayStorage内容的后面

验证Array的底层结构

还是文章开头的代码

var num: Array = [1, 2, 3]
withUnsafePointer(to: &num) {
    print($0)
}
print("end")

print("end")打上断点,输出下num的内存

0x0000000100008060
(lldb) x/8g 0x0000000100008060
0x100008060: 0x000000010064b8d0 0x0000000000000000
0x100008070: 0x0000000000000000 0x0000000000000000
0x100008080: 0x0000000000000000 0x0000000000000000
0x100008090: 0x0000000000000000 0x0000000000000000

0x000000010064b8d0就是_ContiguousArrayStorage的引用,再继续输出0x000000010064b8d0的内存

0x0000000100008060
(lldb) x/8g 0x0000000100008060
0x100008060: 0x000000010064b8d0 0x0000000000000000
0x100008070: 0x0000000000000000 0x0000000000000000
0x100008080: 0x0000000000000000 0x0000000000000000
0x100008090: 0x0000000000000000 0x0000000000000000
(lldb) x/8g 0x000000010064b8d0
0x10064b8d0: 0x000000020dfaf140 0x0000000000000003
0x10064b8e0: 0x0000000000000003 0x0000000000000006
0x10064b8f0: 0x0000000000000001 0x0000000000000002
0x10064b900: 0x0000000000000003 0x0000000000000000
(lldb)

验证成功✅

扩展

Array的写时复制

写时复制:只有需要改变的时候,才会对变量进行复制,如果不改变,数据共用一个内存

Swift标注库中,像集合类对象Array,DictionarySet时通过写时复制copy-on-write的技术实现的

我们查看下源码,依然是写测试代码

var num: Array = [1, 2, 3]
var copyNum = num
num.append(4)

然后在append处打上断点

// File: ArraySlice.swift, line: 919


@inlinable
@_semantics("array.append_element")
public mutating func append(_ newElement: __owned Element) {
  _makeUniqueAndReserveCapacityIfNotUnique()
  let oldCount = _getCount()
  _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
  _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
  _endMutation()
}

一共调用了3个方法,我们依次看下

_makeUniqueAndReserveCapacityIfNotUnique(),直接翻译函数名,如果该数组不是唯一的,那么使得成为唯一并且保留容量,那么这个唯一是什么意思?,这里断点深,只看关键代码

// File: RefCount.h, line: 606


// Compiler is clever enough to optimize this.
  return
    !getUseSlowRC() && !getIsDeiniting() && getStrongExtraRefCount() == 0;

这些都是引用计数的判断,最主要的是getStrongExtraRefCount强引用计数是否为0,如果不是0的话不是唯一的,所以这里的唯一是指对这块空间的唯一引用;如果不唯一会如何处理

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


// File: Array.swift, line: 1069


/// Creates a new buffer, replacing the current buffer.
///
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
/// referenced by this array and the elements are moved - instead of copied -
/// to the new buffer.
/// The `minimumCapacity` is the lower bound for the new capacity.
/// If `growForAppend` is true, the new capacity is calculated using
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
@_alwaysEmitIntoClient
internal mutating func _createNewBuffer(
  bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
) {
  _internalInvariant(!bufferIsUnique || _buffer.isUniquelyReferenced())
  _buffer = _buffer._consumeAndCreateNew(bufferIsUnique: bufferIsUnique,
                                         minimumCapacity: minimumCapacity,
                                         growForAppend: growForAppend)
}

// File: ArrayBuffer.swift, line: 162


/// Creates and returns a new uniquely referenced buffer which is a copy of
/// this buffer.
///
/// This buffer is consumed, i.e. it's released.
@_alwaysEmitIntoClient
@inline(never)
@_semantics("optimize.sil.specialize.owned2guarantee.never")
internal __consuming func _consumeAndCreateNew() -> _ArrayBuffer {
  return _consumeAndCreateNew(bufferIsUnique: false,
                              minimumCapacity: count,
                              growForAppend: false)
}

/// Creates and returns a new uniquely referenced buffer which is a copy of
/// this buffer.
///
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
/// referenced and the elements are moved - instead of copied - to the new
/// buffer.
/// The `minimumCapacity` is the lower bound for the new capacity.
/// If `growForAppend` is true, the new capacity is calculated using
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
///
/// This buffer is consumed, i.e. it's released.
@_alwaysEmitIntoClient
@inline(never)
@_semantics("optimize.sil.specialize.owned2guarantee.never")
internal __consuming func _consumeAndCreateNew(
  bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
) -> _ArrayBuffer {
  let newCapacity = _growArrayCapacity(oldCapacity: capacity,
                                       minimumCapacity: minimumCapacity,
                                       growForAppend: growForAppend)
  let c = count
  _internalInvariant(newCapacity >= c)

  let newBuffer = _ContiguousArrayBuffer(
    _uninitializedCount: c, minimumCapacity: newCapacity)

  if bufferIsUnique {
    // 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: mutableFirstElementAddress,
                        count: c)
    _native.mutableCount = 0
  } else {
    _copyContents(
      subRange: 0..

我们可以清楚地看到会调用_createNewBuffer方法,而_createNewBuffer方法根据一个条件判断去调用_buffer_consumeAndCreateNew方法,这个方法就会生成一个新的buffer

let newBuffer = _ContiguousArrayBuffer(
    _uninitializedCount: c, minimumCapacity: newCapacity)

相当于开辟了一个新的堆空间用于被修改后的数组,所以写时复制的本质就是查看_ContiguousArrayStorage的强引用计数

  • 新创建一个数组num_ContiguousArrayStorage强引用计数是0

  • 此刻num添加元素,发现_ContiguousArrayStorage的强引用计数是0,说明自己时唯一的引用,所以直接空间末尾添加元素就行

  • 当用copyNum复制数组num时,不过是把num_ContiguousArrayStorage复制给了copyNumcopyNum_ContiguousArrayStoragenum_ContiguousArrayStorage的是同一个,不过_ContiguousArrayStorage的强引用计数变为1了,因为这里没有开辟新的空间,非常节省空间

  • 此刻数组num再次添加元素,发现_ContiguousArrayStorage的强引用计数为1,不为0,说明自己不是唯一引用,开辟新的空间,新建一个_ContiguousArrayStorage,复制原有数组内容到新的空间

验证结论,输入以下代码

var num: Array = [1, 2, 3]
withUnsafePointer(to: &num) {
    print($0)
}
var copyNum = num
num.append(4)
print("end")

总结

Swift的数组虽然是Struct类型,但是数组的存放还是在堆空间

Swift的数组的写时复制特性是根据堆空间的强引用计数是否唯一,而唯一的判断强引用计数是否等于0,如果不等于0才会去开辟新的空间进行存储

参考文档

  • NSMutableArray原理揭露:http://blog.joyingx.me/2015/05/03/NSMutableArray%20%E5%8E%9F%E7%90%86%E6%8F%AD%E9%9C%B2/)

  • 探索Swift中Array的底层实现:https://juejin.cn/post/6931236309176418311

你可能感兴趣的:(swift,xcode,开发语言,ios,macos)