本文字数: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是什么
Array
是Swift
下数组的实现,了解Swfit
的都知道,Swift
下的大多数的对象均是由struct
组成的,我们找到源码中的Array
的定义
// File: Array.swift, line: 299
@frozen public struct Array<Element> : Swift._DestructorSafeContainer {
// ...
}
所以Array
在Swift
下本质就是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")
处打断点
x/8g
是LLDB(Low Level Debugger)
下的调试命令,作用是查看内存地址里的值
从图中可以看到,并没有找到1,2,3
的信息,内存里面只有0x0000000101046c70
,猜测是内存某块区域上的地址,所以现在的疑问也有了
Array
保存的地址是什么?Array
保存的数据去哪了?Array
的写复制如何实现的?
带着这三个疑问我们继续往下探索...
生成Array
的SIL
文件
首先我刚才代码文件修改为如下所示,最简单的初始化,有利于我们阅读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
_ContiguousArrayStorage
是class
,查看源码_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)
,看函数名是在初始化count
和capacity
,在_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
,Dictionary
和Set
时通过写时复制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
复制给了copyNum
,copyNum
的_ContiguousArrayStorage
与num
的_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 = 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