關於 ContiguousArray
,這邊有喵神的文章介紹的很詳細了,可以先看看這個文章。
Array
接著喵神的思路,看一下 Array
以下是從原始碼中擷取的程式碼片段。
public struct Array<Element>: _DestructorSafeContainer {
#if _runtime(_ObjC)
internal typealias _Buffer = _ArrayBuffer<Element>
#else
internal typealias _Buffer = _ContiguousArrayBuffer<Element>
#endif
internal var _buffer: _Buffer
internal init(_buffer: _Buffer) {
self._buffer = _buffer
}
}
複製程式碼
if _runtime(_ObjC)
等價於 #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
,從這個操作也可以看出 Swift
的野心不僅僅只是替換 Objective-C
那麼簡單,而是往更加寬泛的方向發展。由於本次主要是研究在 iOS
下的開發,所以主要看一下 _ArrayBuffer
。
_ArrayBuffer
去掉了註釋和與型別檢查相關的屬性和方法。
internal typealias _ArrayBridgeStorage
= _BridgeStorage<_ContiguousArrayStorageBase, _NSArrayCore>
internal struct _ArrayBuffer<Element> : _ArrayBufferProtocol {
internal init() {
_storage = _ArrayBridgeStorage(native: _emptyArrayStorage)
}
internal var _storage: _ArrayBridgeStorage
}
複製程式碼
可見 _ArrayBuffer
僅有一個儲存屬性 _storage
,它的型別 _ArrayBridgeStorage
,本質上是 _BridgeStorage
。
_NSArrayCore
其實是一個協議,定義了一些 NSArray
的方法,主要是為了橋接 Objective-C
的 NSArray
。
最主要的初始化函式,是通過 _emptyArrayStorage
來初始化 _storage
。
實際上 _emptyArrayStorage
是 _EmptyArrayStorage
的例項,主要作用是初始化一個空的陣列,並且將記憶體指定在堆上。
internal var _emptyArrayStorage : _EmptyArrayStorage {
return Builtin.bridgeFromRawPointer(
Builtin.addressof(&_swiftEmptyArrayStorage))
}
複製程式碼
_BridgeStorage
struct _BridgeStorage<NativeClass: AnyObject, ObjCClass: AnyObject> {
typealias Native = NativeClass
typealias ObjC = ObjCClass
init(native: Native, bits: Int) {
rawValue = _makeNativeBridgeObject(
native, UInt(bits) << _objectPointerLowSpareBitShift)
}
init(objC: ObjC) {
rawValue = _makeObjCBridgeObject(objC)
}
init(native: Native) {
rawValue = Builtin.reinterpretCast(native)
}
internal var rawValue: Builtin.BridgeObject
複製程式碼
_BridgeStorage
實際上區分 是否是 class、@objc
,進而提供不同的儲存策略,為上層呼叫提供了不同的介面,以及型別判斷,通過 Builtin.BridgeObject
這個中間引數,實現不同的儲存策略。
The ContiguousArray type is a specialized array that always stores its elements in a contiguous region of memory. This contrasts with Array, which can store its elements in either a contiguous region of memory or an NSArray instance if its Element type is a class or @objc protocol.
If your array’s Element type is a class or @objc protocol and you do not need to bridge the array to NSArray or pass the array to Objective-C APIs, using ContiguousArray may be more efficient and have more predictable performance than Array. If the array’s Element type is a struct or enumeration, Array and ContiguousArray should have similar efficiency.
正因為儲存策略的不同,特別是在class
或者 @objc
,如果不考慮橋接到 NSArray
或者呼叫 Objective-C
,蘋果建議我們使用 ContiguousArray
,會更有效率。
Array 和 ContiguousArray 區別
通過一些常用的陣列操作,來看看兩者之間的區別。
append
ContiguousArray
public mutating func append(_ newElement: Element) {
_makeUniqueAndReserveCapacityIfNotUnique()
let oldCount = _getCount()
_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
_appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
}
internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
_copyToNewBuffer(oldCount: _buffer.count)
}
}
internal mutating func _reserveCapacityAssumingUniqueBuffer(oldCount: Int) {
let capacity = _buffer.capacity == 0
if _slowPath(oldCount + 1 > _buffer.capacity) {
_copyToNewBuffer(oldCount: oldCount)
}
}
internal mutating func _copyToNewBuffer(oldCount: Int) {
let newCount = oldCount + 1
var newBuffer = _buffer._forceCreateUniqueMutableBuffer(
countForNewBuffer: oldCount, minNewCapacity: newCount)
_buffer._arrayOutOfPlaceUpdate(
&newBuffer, oldCount, 0, _IgnorePointer())
}
internal mutating func _appendElementAssumeUniqueAndCapacity(
_ oldCount: Int,
newElement: Element
) {
_buffer.count = oldCount + 1
(_buffer.firstElementAddress + oldCount).initialize(to: newElement)
}
複製程式碼
_makeUniqueAndReserveCapacityIfNotUnique()
檢查陣列是否是唯一持有者,以及是否是可變陣列。
_reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
檢查陣列內的元素個數加一後,是否超出超過所分配的空間。
前兩個方法在檢查之後都呼叫了 _copyToNewBuffer
,主要操作是如果當前陣列需要申請空間,則申請空間,然後再複製 buffer
。
_appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
從首地址後的第 oldCount
個儲存空間內,初始化 newElement
。
Array
Array
實現的過程與 ContiguousArray
差不多,但是還是有一些區別,具體看看,主要的區別存在於_ContiguousArrayBuffer
和 _ArrayBuffer
_ContiguousArrayBuffer
internal var firstElementAddress: UnsafeMutablePointer<Element> {
return UnsafeMutablePointer(Builtin.projectTailElems(_storage,
Element.self))
}
複製程式碼
直接返回了記憶體地址。
_ArrayBuffer
internal var firstElementAddress: UnsafeMutablePointer<Element> {
_sanityCheck(_isNative, "must be a native buffer")
return _native.firstElementAddress
}
internal var _native: NativeBuffer {
return NativeBuffer(
_isClassOrObjCExistential(Element.self)
? _storage.nativeInstance : _storage.nativeInstance_noSpareBits)
}
internal typealias NativeBuffer = _ContiguousArrayBuffer<Element>
複製程式碼
從呼叫的情況來看,本質上還是呼叫了 _ContiguousArrayBuffer
的firstElementAddress
但是在建立時,會有型別檢查。
_isClassOrObjCExistential(Element.self)
檢查是否是類或者@objc
修飾的。
在上述中檢查持有者是否唯一和陣列是否可變的函式中, 其實是呼叫了 _buffer
內部的 isMutableAndUniquelyReferenced()
。
_ContiguousArrayBuffer
@inlinable
internal mutating func isUniquelyReferenced() -> Bool {
return _isUnique(&_storage)
}
複製程式碼
internal func _isUnique<T>(_ object: inout T) -> Bool {
return Bool(Builtin.isUnique(&object))
}
複製程式碼
最後呼叫的 Builtin
中的 isUnique
。
_ArrayBuffer
internal mutating func isUniquelyReferenced() -> Bool {
if !_isClassOrObjCExistential(Element.self) {
return _storage.isUniquelyReferenced_native_noSpareBits()
}
if !_storage.isUniquelyReferencedNative() {
return false
}
return _isNative
}
mutating func isUniquelyReferencedNative() -> Bool {
return _isUnique(&rawValue)
}
mutating func isUniquelyReferenced_native_noSpareBits() -> Bool {
_sanityCheck(isNative)
return _isUnique_native(&rawValue)
}
func _isUnique_native<T>(_ object: inout T) -> Bool {
_sanityCheck(
(_bitPattern(Builtin.reinterpretCast(object)) & _objectPointerSpareBits)
== 0)
_sanityCheck(_usesNativeSwiftReferenceCounting(
type(of: Builtin.reinterpretCast(object) as AnyObject)))
return Bool(Builtin.isUnique_native(&object))
}
複製程式碼
如果是 class 或者 @objc
和 _ContiguousBuffer
一樣。如果不是則需要呼叫 Builtin
中的 _isUnique_native
,即要檢查是否唯一,還要檢查是否是 Swift
原生 而不是 NSArray
。
相對於 _ContiguousArrayBuffer
由於 _ArrayBuffer
承載了需要橋接到 NSArray
的功能,所以多了一些型別檢查的操作。
insert
ContiguousArray
//ContiguousArray
public mutating func insert(_ newElement: Element, at i: Int) {
_checkIndex(i)
self.replaceSubrange(i..<i, with: CollectionOfOne(newElement))
}
public mutating func replaceSubrange<C>(
_ subrange: Range<Int>,
with newElements: C
) where C : Collection, C.Element == Element {
let oldCount = _buffer.count
let eraseCount = subrange.count
let insertCount = newElements.count
let growth = insertCount - eraseCount
if _buffer.requestUniqueMutableBackingBuffer(
minimumCapacity: oldCount + growth) != nil {
_buffer.replaceSubrange(
subrange, with: insertCount, elementsOf: newElements)
} else {
_buffer._arrayOutOfPlaceReplace(subrange, with: newElements, count: insertCount)
}
}
internal mutating func requestUniqueMutableBackingBuffer(
minimumCapacity: Int
) -> _ContiguousArrayBuffer<Element>? {
if _fastPath(isUniquelyReferenced() && capacity >= minimumCapacity) {
return self
}
return nil
}
//extension ArrayProtocol
internal mutating func replaceSubrange<C>(
_ subrange: Range<Int>,
with newCount: Int,
elementsOf newValues: C
) where C : Collection, C.Element == Element {
_sanityCheck(startIndex == 0, "_SliceBuffer should override this function.")
let oldCount = self.count //現有陣列大小
let eraseCount = subrange.count //需要替換大小
let growth = newCount - eraseCount //目標大小 和 需要替換大小 的差值
self.count = oldCount + growth //替換後的陣列大小
let elements = self.subscriptBaseAddress //陣列首地址。
let oldTailIndex = subrange.upperBound
let oldTailStart = elements + oldTailIndex //需要替換的尾地址。
let newTailIndex = oldTailIndex + growth //需要增加的空間的尾下標
let newTailStart = oldTailStart + growth //需要增加的空間的尾地址
let tailCount = oldCount - subrange.upperBound //需要移動的記憶體空間大小
if growth > 0 {
var i = newValues.startIndex
for j in subrange {
elements[j] = newValues[i]
newValues.formIndex(after: &i)
}
for j in oldTailIndex..<newTailIndex {
(elements + j).initialize(to: newValues[i])
newValues.formIndex(after: &i)
}
_expectEnd(of: newValues, is: i)
}
else {
var i = subrange.lowerBound
var j = newValues.startIndex
for _ in 0..<newCount {
elements[i] = newValues[j]
i += 1
newValues.formIndex(after: &j)
}
_expectEnd(of: newValues, is: j)
if growth == 0 {
return
}
let shrinkage = -growth
if tailCount > shrinkage {
newTailStart.moveAssign(from: oldTailStart, count: shrinkage)
oldTailStart.moveInitialize(
from: oldTailStart + shrinkage, count: tailCount - shrinkage)
}
else {
newTailStart.moveAssign(from: oldTailStart, count: tailCount)
(newTailStart + tailCount).deinitialize(
count: shrinkage - tailCount)
}
}
}
複製程式碼
insert
內部實際是 呼叫了 replaceSubrange
。
而在 replaceSubrange
的操作是,判斷記憶體空間是否夠用,和持有者是否唯一,如果有一個不滿足條件則複製 buffer
到新的記憶體空間,並且根據需求分配好記憶體空間大小。
而 _buffer
內部的 replaceSubrange
:
- 計算
growth
值看所替換的大小和目標大小差值是多少。 - 如果
growth > 0
,則需要將現有的記憶體空間向後移動growth
位。 - 替換所需要替換的值。
- 超出的部分重新分配記憶體並初始化值。
- 如果
growth <= 0
,則將現有的值替換成新的值即可。 - 如果
growth < 0
,則將不需要的記憶體空間回收即可。(ps:刪除多個元素或者需要替換的大小大於目標大小)。
Array
insert
兩者基本一致,唯一的區別和 append
一樣在 在buffer
的內部方法,isUniquelyReferenced()
中,多了一些型別檢查。
remove
ContiguousArray
public mutating func remove(at index: Int) -> Element {
_makeUniqueAndReserveCapacityIfNotUnique()
let newCount = _getCount() - 1
let pointer = (_buffer.firstElementAddress + index)
let result = pointer.move()
pointer.moveInitialize(from: pointer + 1, count: newCount - index)
_buffer.count = newCount
return result
}
複製程式碼
檢查陣列持有者是否唯一,取出所要刪除的記憶體地址,通過將當前的記憶體區域覆蓋為一個未初始化的記憶體空間,以達到回收記憶體空間的作用,進而達到刪除陣列元素的作用。
Array
與 ContiguousArray
的區別就在於 _makeUniqueAndReserveCapacityIfNotUnique()
前面已經提到過,仍然是多了一些型別檢查。
subscript
ContiguousArray
//ContiguousArray
public subscript(index: Int) -> Element {
get {
let wasNativeTypeChecked = _hoistableIsNativeTypeChecked()
let token = _checkSubscript(
index, wasNativeTypeChecked: wasNativeTypeChecked)
return _getElement(
index, wasNativeTypeChecked: wasNativeTypeChecked,
matchingSubscriptCheck: token)
}
}
public
func _getElement(
_ index: Int,
wasNativeTypeChecked : Bool,
matchingSubscriptCheck: _DependenceToken
) -> Element {
#if false
return _buffer.getElement(index, wasNativeTypeChecked: wasNativeTypeChecked)
#else
return _buffer.getElement(index)
#endif
}
//ContiguousArrayBuffer
internal func getElement(_ i: Int) -> Element {
return firstElementAddress[i]
}
複製程式碼
_hoistableIsNativeTypeChecked()
不做任何檢查,直接返回 true
。
_checkSubscript(index, wasNativeTypeChecked: wasNativeTypeChecked)
檢查 index
是否越界。
_getElement
最終還是操作記憶體,通過 firstElementAddress
偏移量取出值。
Array
//Array
public
func _checkSubscript(
_ index: Int, wasNativeTypeChecked: Bool
) -> _DependenceToken {
#if _runtime(_ObjC)
_buffer._checkInoutAndNativeTypeCheckedBounds(
index, wasNativeTypeChecked: wasNativeTypeChecked)
#else
_buffer._checkValidSubscript(index)
#endif
return _DependenceToken()
}
func _hoistableIsNativeTypeChecked() -> Bool {
return _buffer.arrayPropertyIsNativeTypeChecked
}
//ArrayBuffer
internal var arrayPropertyIsNativeTypeChecked: Bool {
return _hasNativeBuffer
}
internal var _isNativeTypeChecked: Bool {
if !_isClassOrObjCExistential(Element.self) {
return true
} else {
return _storage.isNativeWithClearedSpareBits(deferredTypeCheckMask)
}
}
複製程式碼
在 ContiguousArray
中 _hoistableIsNativeTypeChecked()
直接返回 true
, 而 Array
中如果不是 class 或者 @objc
會返回 ture
,否則會檢查是否可以橋接到 Swift
。
而在 Array
中 _checkSubscript
呼叫的 _buffer
內部函式也不一樣,下面來具體看一看內部實現。
//ArrayBuffer
internal func _checkInoutAndNativeTypeCheckedBounds(
_ index: Int, wasNativeTypeChecked: Bool
) {
_precondition(
_isNativeTypeChecked == wasNativeTypeChecked,
"inout rules were violated: the array was overwritten")
if _fastPath(wasNativeTypeChecked) {
_native._checkValidSubscript(index)
}
}
//ContiguousArrayBuffer
internal func _checkValidSubscript(_ index : Int) {
_precondition(
(index >= 0) && (index < count),
"Index out of range"
)
}
複製程式碼
本質上就是多了一些是否是型別檢查。
//Array
func _getElement(
_ index: Int,
wasNativeTypeChecked : Bool,
matchingSubscriptCheck: _DependenceToken
) -> Element {
#if _runtime(_ObjC)
return _buffer.getElement(index, wasNativeTypeChecked: wasNativeTypeChecked)
#else
return _buffer.getElement(index)
#endif
}
//ArrayBuffer
internal func getElement(_ i: Int, wasNativeTypeChecked: Bool) -> Element {
if _fastPath(wasNativeTypeChecked) {
return _nativeTypeChecked[i]
}
return unsafeBitCast(_getElementSlowPath(i), to: Element.self)
}
internal func _getElementSlowPath(_ i: Int) -> AnyObject {
let element: AnyObject
if _isNative {
_native._checkValidSubscript(i)
element = cast(toBufferOf: AnyObject.self)._native[i]
} else {
element = _nonNative.objectAt(i)
}
return element
}
//ContiguousArrayBuffer
internal subscript(i: Int) -> Element {
get {
return getElement(i)
}
}
複製程式碼
在 _buffer
內部的 getElement
, 與 ContiguousArray
不同的是需要適配橋接到 NSArray
的情況,如果是 非NSArray
的情況呼叫的是 ContiguousArrayBuffer
內部的 subscript
,和 ContiguousArray
相同。
總結
從增刪改查來看,不管是 ContiguousArray
還是 Array
最終都是操作記憶體,稍顯區別的就是 Array
需要更多的型別檢查。所以當不需要 Objective-C
,還是儘量使用 ContiguousArray
。
下面是對陣列中一些批量操作的總結:
removeAll
、insert<C>(contentsOf: C, at: Int)
、removeSubrange
:最終呼叫的是replaceSubrange
append<S : Sequence>(contentsOf newElements: S)
和init(repeating repeatedValue: Element, count: Int)
:最終都是操作記憶體,迴圈初始化新的記憶體空間和值。
有什麼不正確的地方,歡迎指出。