窥探Swift源码下的Array

news2024/11/23 22:38:29

db2edc7d0b58b4e87588b56e4882f96b.jpeg

 11582c69c97f6a69aa9c55192be5e8d4.gif 

本文字数: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<Element> : Swift._DestructorSafeContainer {
  
  // ...
}

所以ArraySwift下本质就是Struct

Array有什么用

有序的存储一组数据

Array的底层原理

新建一个项目

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

输入以下代码

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

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

d6eb7edafac3693e02641e522ae982c7.jpeg

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

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

  • Array保存的地址是什么?

  • Array保存的数据去哪了?

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

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

生成ArraySIL文件

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

var num: Array<Int> = [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<Int> { get set }

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

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.num : [Swift.Int]               // id: %2,在堆上开辟一个空间
  %3 = global_addr @main.num : [Swift.Int] : $*Array<Int> // user: %25,创建临时变量%3存放数组首地址
  %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: %24
  %8 = tuple_extract %6 : $(Array<Int>, 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<A>(_:)
  %23 = function_ref @Swift._finalizeUninitializedArray<A>(__owned [A]) -> [A] : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %24
  %24 = apply %23<Int>(%7) : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %25
  store %24 to %3 : $*Array<Int>                  // 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<A>(_:)
sil [always_inline] [_semantics "array.uninitialized_intrinsic"] @Swift._allocateUninitializedArray<A>(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<A>(_:)
sil shared_external [readnone] [_semantics "array.finalize_intrinsic"] @Swift._finalizeUninitializedArray<A>(__owned [A]) -> [A] : $@convention(thin) <Element> (@owned Array<Element>) -> @owned Array<Element> {
// %0                                             // user: %2
bb0(%0 : $Array<Element>):
  %1 = alloc_stack $Array<Element>                // users: %6, %5, %4, %2
  store %0 to %1 : $*Array<Element>               // 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<Element>(%1) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> ()
  %5 = load %1 : $*Array<Element>                 // user: %7
  dealloc_stack %1 : $*Array<Element>             // id: %6
  return %5 : $Array<Element>                     // id: %7
} // end sil function 'Swift._finalizeUninitializedArray<A>(__owned [A]) -> [A]'

// Array._endMutation()
sil shared_external [_semantics "array.end_mutation"] @Swift.Array._endMutation() -> () : $@convention(method) <Element> (@inout Array<Element>) -> () {
// %0                                             // users: %9, %1
bb0(%0 : $*Array<Element>):
  %1 = struct_element_addr %0 : $*Array<Element>, #Array._buffer // user: %2
  %2 = struct_element_addr %1 : $*_ArrayBuffer<Element>, #_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<Element> (%6 : $_BridgeStorage<__ContiguousArrayStorageBase>) // user: %8
  %8 = struct $Array<Element> (%7 : $_ArrayBuffer<Element>) // user: %9
  store %8 to %0 : $*Array<Element>               // 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<A>()的方法,该方法的返回值是一个元祖%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<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
  }
}

我们可以看到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<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,走的不同的方法,但是返回值都是一样的,我们只看其中一个,因为例子中的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<Element>, count: Int
) -> (Array, UnsafeMutablePointer<Element>) {

  let innerBuffer = _ContiguousArrayBuffer<Element>(
    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<Element>) {
  _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<Element>)中传进来的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<Element>: _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<NativeClass: AnyObject> {
  @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<Element> {
  return UnsafeMutablePointer(Builtin.projectTailElems(_storage,
                                                       Element.self))
}

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

// File: Builtins.def, line: 465

/// projectTailElems : <C,E> (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<Int> = [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<Int> = [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<Element>(
    _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..<c,
      initializing: newBuffer.mutableFirstElementAddress)
  }
  return _ArrayBuffer(_buffer: newBuffer, shiftedToStartIndex: 0)
}

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

let newBuffer = _ContiguousArrayBuffer<Element>(
    _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<Int> = [1, 2, 3]
withUnsafePointer(to: &num) {
    print($0)
}
var copyNum = num
num.append(4)
print("end")
  • 分别在var copyNum = numnum.append(4)print("end")打上断点

  • 3019844354675ab9f0e8e504194c5088.jpeg


总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis -- IO多路复用及redis6的多线程

都知道redis是通过单线程io多路复用来避免并发问题的&#xff0c;然后在redis6的时候redis引入了多线程&#xff0c;这里就来详细说说IO多路复用模型以及redis的多线程。 Redis 的 I/O 多路复用模型有效的解决单线程的服务端&#xff0c;使用不阻塞方式处理多个 client 端请求…

optional妙用解决NullPointerException

背景 作为一个java程序员&#xff0c;我们在日常开发中经常会碰到一个臭名昭著空异常&#xff0c;所有程序员都经历过的痛点&#xff0c;空指针异常&#xff08;NullPointerException&#xff09; java8中出现的Optional是java开始迈向函数式编程的一大步&#xff0c;也有效的…

实验 4 图像复原与重建

目录一、实验目的二、实验例题1. 噪声模型2. 只存在噪声的滤波——空间滤波3. 退化函数建模4. 存在线性退化和噪声的滤波(1) 逆滤波(2) 最小均方误差(维纳)滤波附录一、实验目的 了解一些常用随机噪声的生成方法。掌握根据指定退化函数对图像进行退化的方法。掌握当模糊图像只…

十一、Gtk4-Instance Initialization and destruction

文本文件编辑器(tfe)的新版本将在本节和以下四节中编写。它是tfe5。与之前的版本相比&#xff0c;有很多变化。它们位于两个目录中&#xff0c;src/tfe5和src/tfetextview。 1 封装 我们将C源文件分为两部分。但就封装而言&#xff0c;这还不够。 tfe.c包含除Tfe TextView之…

【Linux】线程控制

目录&#x1f308;前言&#x1f338;1、Linux线程控制&#x1f361;1.1、创建线程(pthread_create)&#x1f362;1.2、通过指令查看线程PID和LWP&#x1f367;1.3、获取线程ID(pthread_self)&#x1f368;1.4、线程终止(pthread_exit/cancel)&#x1f368;1.5、线程等待(pthrea…

一文读懂PCB阻焊工艺

PCB阻焊油墨根据固化方式&#xff0c;阻焊油墨有感光显影型的油墨&#xff0c;有热固化的热固油墨&#xff0c;还有UV光固化的UV油墨。而根据板材分类&#xff0c;又有PCB硬板阻焊油墨&#xff0c;FPC软板阻焊油墨&#xff0c;还有铝基板阻焊油墨&#xff0c;铝基板油墨也可以用…

力扣算法(Java实现)—数组入门(11题)

文章目录1.删除排序数组中的重复项2.买卖股票的最佳时机 II3.旋转数组4.存在重复元素5.找出只出现一次的元素6.两个数组的交集7.移动零8.加一9.两数之和10.有效的数独11.旋转图像&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e; 更多资源链接&#xff0c;欢…

【手写 Vue2.x 源码】第十八篇 - 根据 render 函数,生成 vnode

一&#xff0c;前言 上篇&#xff0c;介绍了render 函数的生成&#xff0c;主要涉及以下两点&#xff1a; 使用 with 对生成的 code 进行一次包装将包装后的完整 code 字符串&#xff0c;通过 new Function 输出为 render 函数 本篇&#xff0c;根据 render 函数&#xff0c…

linux系统中QT里面的视频播放器的实现方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用QT中视频播放器的方法。 目录 第一&#xff1a;视频播放器基本简介 第二&#xff1a;视频播放器头文件说明 第三&#xff1a;源文件的具体实现方法 第四&#xff1a;运行效果显示 第一&#xff1a;视频播放器基本…

ADS振铃仿真

目录 无振铃时的原理图 无振铃时的Vout和VL输出波形 ​LineCalc对微带线阻抗的计算结果 将微带线线宽Width统一由116改为130 将微带线线宽Width统一由116改为80 将微带线TL9线宽由116改为300 将微带线TL9线宽由116改为50 本文介绍了微带线线宽变化时100MHz信号的反射现象…

2023 年 15 大测试自动化趋势

在过去&#xff0c;软件测试只是为了发现软件产品中的错误。目标是——提高软件质量。但如今&#xff0c;软件测试的范围已经扩大。在软件测试方面&#xff0c;自动化测试一直走在前列。按照最新的测试自动化趋势&#xff0c;软件测试行业有望比过去十年发展得更快。 根据 Mar…

Java面向对象综合训练

Java面向对象综合训练一、文字版格斗游戏Role类测试类输出结果二、对象数组练习对象数组1商品类测试类输出结果对象数组2汽车类测试类输出结果对象数组3手机类测试类输出结果对象数组4女朋友类测试类输出结果对象数组5学生类测试类输出结果一、文字版格斗游戏 Role类 import j…

去掉 域名后面的 /#/ vue-router 和 hbuilder发布 web项目和h5项目

1. vue-router vue-router默认的路由模式是hash&#xff0c;我们要去掉url中的#需要将路由模式切换为history const router new VueRouter({base: test, // 如果项目项目在 域名 根目录下&#xff0c;则去掉这行mode: history, // 路由模式... })这样子&#xff0c;url中的#…

为什么ERP和项目管理的集成是必要的?

在一个企业中&#xff0c;传统的责任分工意味着会计人员看管资金和维持财务标准&#xff0c;而职能经理分配人力资源和维持技术标准。项目经理指导分配的资金和其他资源&#xff0c;同时努力实现项目目标。每个学科都有自己的业务规则&#xff0c;自己的做法&#xff0c;自己的…

C++ | 左值、右值、将亡值和引用的概念 | 聊聊我对它们的深入理解

文章目录前言左右值的辨析一个特殊的问题将亡值引用的深刻理解前言 这篇文章是我在探究完美转发这个语法点时&#xff0c;引发的相关问题思考&#xff0c;为了使自己的理解更深刻&#xff0c;故写下这篇博客 左右值的辨析 首先需要明白两个概念&#xff1a;类型&#xff08;…

1577_AURIX_TC275_MTU中检测控制相关寄存器

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 开篇介绍的功能室之前看过很多次的一个握手的功能。快速行以及快速列模式的测试中&#xff0c;这个行列其实是对应的存储的bit阵列信息。一个对应相应的字&#xff0c;另一个则对应bit序列…

【Linux】进程创建、终止、等待、替换、shell派生子进程的理解…

柴犬&#xff1a; 你好啊&#xff0c;屏幕前的大帅哥or大美女&#xff0c;和我一起享受美好的今天叭&#x1f603;&#x1f603;&#x1f603; 文章目录一、进程创建1.调用fork之后&#xff0c;内核都做了什么&#xff1f;2.如何理解fork函数有两个返回值&#xff1f;3.如何理…

(短信服务)java SpringBoot 阿里云短信功能实现发送手机验证码

一.阿里云准备工作 1.阿里云短信服务-注册账号 阿里云官网: https://www.aliyun.com/ 点击官网首页注册按钮。 2.阿里云短信服务-设置短信签名&#xff08;阿里云提供测试的签名&#xff0c;暂时可以跳过&#xff09; 注册成功后&#xff0c;点击登录按钮进行登录。登录后…

简单方式调用WebService服务

好久没有进行过WebService开发了&#xff0c;由于项目需要&#xff0c;重拾WebService&#xff0c;记录一下简单的服务调用方法。拿到需求&#xff0c;仅半页word&#xff0c;其他的就没有了&#xff0c;为了快速开发&#xff0c;尝试过使用插件逆向生成调用的一大堆类&#xf…

AWVS安装与激活

AWVS安装与激活 1.AWVS简介 AWVS&#xff08;Acunetix Web Vulnerability Scanner&#xff09;是一款知名的网络漏洞扫描工具&#xff0c;通过网络爬虫测试网站安全&#xff0c;检测流行的Web应用攻击&#xff0c;如跨站脚本、sql 注入等。据统计&#xff0c;75% 的互联网攻击…