Swift探索( 十二): Array源碼分析

一:Array 的內(nèi)存布局

SwiftArray 其實(shí)是用結(jié)構(gòu)體實(shí)現(xiàn)的,所以 Array 是值類(lèi)型县踢。

Array.png

通過(guò)直接打印 Array 可可以看出來(lái) Array 是值類(lèi)型

let array = [1, 2, 3, 4, 5]
print(MemoryLayout.stride(ofValue: array))

let array2 = [1, 2, 3, 4, 5, 6]
print(MemoryLayout.stride(ofValue: array2))

struct Person {
    var age = 18
    var height = 180
}

let p = Person()
print(MemoryLayout.stride(ofValue: p))

// 打印結(jié)果 
8
8
16

我們知道結(jié)構(gòu)體的大小取決于結(jié)構(gòu)體成員變量的大小屑彻。但是這里的 arrayarray1 的大小都是 8症昏,這是為什么呢蛙粘?這里只能說(shuō)明一個(gè)問(wèn)題,那就是數(shù)組的元素不是存儲(chǔ)在 array 變量上的威彰,那數(shù)組在中數(shù)據(jù)存儲(chǔ)在什么地方出牧?我們通過(guò)匯編代碼來(lái)看 array 的初始化。

array 的初始化匯編代碼.png

這里可以看到 array 初始化的時(shí)候調(diào)用了 _allocateUninitializedArray 這個(gè)方法抱冷,并且傳入了一個(gè)參數(shù)崔列。那么,這個(gè)方法在內(nèi)部都做了什么操作呢旺遮,我們來(lái)看一下源碼中它是如何實(shí)現(xiàn)的赵讯。
ArrayShared.swift 文件中找到這個(gè)函數(shù)的具體實(shí)現(xiàn):

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

這里可以看到傳入的參數(shù)是 Builtin.Word 類(lèi)型的 builtinCount 通過(guò)語(yǔ)義可以得到這個(gè)參數(shù)應(yīng)該就是數(shù)組元素的個(gè)數(shù)。

  • 這個(gè)方法返回一個(gè)元組 第一個(gè)參數(shù)是數(shù)組 Array< Element > 耿眉, 第二次參數(shù)是一個(gè)指針边翼。
  • 通過(guò) count 判斷當(dāng)前初始化的數(shù)組是否需要分配緩沖區(qū)。
  • 當(dāng)大于 0 的時(shí)候鸣剪,會(huì)調(diào)用一個(gè) allocWithTailElems_1 方法组底,分配堆空間來(lái)存儲(chǔ)數(shù)組當(dāng)中的元素。接著通過(guò) _adoptStorage 來(lái)創(chuàng)建數(shù)組筐骇。
  • 當(dāng)小于 0 的時(shí)候债鸡,會(huì)調(diào)用 _allocateUninitialized 來(lái)創(chuàng)建一個(gè)空數(shù)組。

Array.Swift 文件中知道了 _adoptStorage 方法的實(shí)現(xiàn)

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

這里可以看到返回的是一個(gè)元祖

  • 第一個(gè)元素是: Array(_buffer: _Buffer(_buffer: innerBuffer, shiftedToStartIndex: 0))
  • 第二個(gè)元素是:數(shù)組第一個(gè)元素的地址铛纬。

這里為什么還要返回第一個(gè)元素首地址呢厌均?難道 Array 的地址并不是指向存儲(chǔ)的第一個(gè)元素?
我們可以看到這里的 Array 調(diào)用的是 init(_buffer: _Buffer) 這個(gè)初始化方法傳入的是 _ContiguousArrayBuffer< Element > 類(lèi)型的 innerBuffer

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

可以看到這里的 _buffer 是作為 Array 的成員變量,那么 Array 的內(nèi)存地址肯定是有這個(gè) _buffer 的空間的告唆。

ContiguousArrayBuffer.swift 文件中找到 _ContiguousArrayBuffer 的聲明

@usableFromInline
@frozen
internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol {
  @usableFromInline
  internal var _storage: __ContiguousArrayStorageBase
...

發(fā)現(xiàn) _ContiguousArrayBuffer 有一個(gè) __ContiguousArrayStorageBase 類(lèi)型的成員變量 _storage

SwiftNativeNSArray.swift 文件中找到 __ContiguousArrayStorageBase 的聲明

internal class __ContiguousArrayStorageBase
  : __SwiftNativeNSArrayWithContiguousStorage {

  @usableFromInline
  final var countAndCapacity: _ArrayBody

  @inlinable
  @nonobjc
  internal init(_doNotCallMeBase: ()) {
    _internalInvariantFailure("creating instance of __ContiguousArrayStorageBase")
  }
...

發(fā)現(xiàn) __ContiguousArrayStorageBase 是一個(gè)繼承自 __SwiftNativeNSArrayWithContiguousStorage 的類(lèi)棺弊,并且有一個(gè) _ArrayBody 類(lèi)型的成員變量 countAndCapacity

在之前的 Swift探索(一): 類(lèi)與結(jié)構(gòu)體(上) 的文章中我們就研究過(guò)類(lèi)的內(nèi)存結(jié)構(gòu)是由 metadatarefCount 組成擒悬。

ArrayBody.swift 文件中 定位到 _ArrayBody 的聲明

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

發(fā)現(xiàn) _ArrayBody 有個(gè) _SwiftArrayBodyStorage 類(lèi)型的成員變量 _storage
GlobalObjects.h 找到 _SwiftArrayBodyStorage 的聲明

struct _SwiftArrayBodyStorage {
  __swift_intptr_t count;
  __swift_uintptr_t _capacityAndFlags;
};

綜上能夠得出數(shù)組類(lèi)型的變量里存儲(chǔ)的其實(shí)是一個(gè)名為 __ContiguousArrayStorageBase 類(lèi)的內(nèi)存地址模她,而這個(gè)類(lèi)存儲(chǔ)著類(lèi)的信息( metadatarefCount)和元素的個(gè)數(shù)( count),容量的大小和標(biāo)志位( _capacityAndFlags )懂牧,以及元素的內(nèi)存地址侈净,大致結(jié)構(gòu)如下。

Array的內(nèi)存結(jié)構(gòu).png

通過(guò) LLDB 的打印查看 Array 的內(nèi)存結(jié)構(gòu)

Array的內(nèi)存結(jié)構(gòu).png

本質(zhì)上 Array 是一個(gè)引用類(lèi)型僧凤,只是在 struct 上嵌套了一個(gè) class用狱。

二:Array 擴(kuò)容

對(duì)于 Array 來(lái)說(shuō)是可以實(shí)時(shí)的通過(guò) append() 方法添加元素

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

那么 append() 方法實(shí)際上干了些什么操作呢?
在源碼中有下面一段注釋

capacity.png

每個(gè)數(shù)組都保留特定數(shù)量的內(nèi)存來(lái)保存其內(nèi)容。 當(dāng)向數(shù)組中添加元素時(shí)拼弃,如果數(shù)組開(kāi)始超過(guò)其預(yù)留容量,則數(shù)組分配更大的內(nèi)存區(qū)域摇展,并將其元素 復(fù)制 到新的存儲(chǔ)中吻氧。 新存儲(chǔ)空間是舊存儲(chǔ)空間的數(shù)倍。 這種指數(shù)增長(zhǎng)策略意味著追加一個(gè)元素的時(shí)間是恒定的,可以平均許多追加操作的性能盯孙。 觸發(fā)重新分配的附加操作會(huì)造成性能損失鲁森,但隨著數(shù)組的增長(zhǎng),它們出現(xiàn)的頻率越來(lái)越低振惰。

接下來(lái)看看 append() 具體是怎么實(shí)現(xiàn)的歌溉。在 Array.swift 文件中找到 append() 方法的實(shí)現(xiàn)

@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 = _buffer.mutableCount
    _reserveCapacityAssumingUniqueBuffer(oldCount: oldCount)
    _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
    _endMutation()
  }

首先調(diào)用了 _makeUniqueAndReserveCapacityIfNotUnique() 方法,定位到 _makeUniqueAndReserveCapacityIfNotUnique() 的實(shí)現(xiàn)

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

可以看見(jiàn)這里有一個(gè)判斷條件 !_buffer.beginCOWMutation() 找到 beginCOWMutation() 的實(shí)現(xiàn)

@_alwaysEmitIntoClient
  internal mutating func beginCOWMutation() -> Bool {
    if !_hasNativeBuffer {
      return false
    }
    if Bool(Builtin.beginCOWMutation(&owner)) {
#if INTERNAL_CHECKS_ENABLED && COW_CHECKS_ENABLED
      nativeBuffer.isImmutable = false
#endif
      return true
    }
    return false;
  }

其實(shí)就是去判斷緩沖區(qū)的存儲(chǔ)是否是被唯一引用的骑晶。如果是緩沖區(qū)的存儲(chǔ)是被多個(gè)引用的痛垛,則將緩沖區(qū)置為可變狀態(tài);如果是被唯一引用桶蛔,則返回 false 匙头,這會(huì)去創(chuàng)建新的 buffer ,通過(guò)判斷條件進(jìn)入 if 的執(zhí)行語(yǔ)句仔雷,也就是去執(zhí)行 _createNewBuffer(bufferIsUnique: false, minimumCapacity: count &+ 1, growForAppend: true)
定位到 _createNewBuffer(bufferIsUnique: minimumCapacity: growForAppend:)

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

這里面調(diào)用的是 _consumeAndCreateNew() 方法蹂析,定位到 _consumeAndCreateNew()

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

這里又調(diào)用了一個(gè)方法 _growArrayCapacity() 接著定位到 _growArrayCapacity()

@inlinable
internal func _growArrayCapacity(_ capacity: Int) -> Int {
  return capacity * 2
}

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

這里可以看到 這里返回的是 minimumCapacityoldCapacity * 2 的最大值。
所以碟婆,一般情況下电抚,一個(gè)數(shù)組在進(jìn)行擴(kuò)容的時(shí)候,本質(zhì)上是在舊的 capacity 上進(jìn)行 2 倍擴(kuò)容竖共。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蝙叛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肘迎,更是在濱河造成了極大的恐慌甥温,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妓布,死亡現(xiàn)場(chǎng)離奇詭異姻蚓,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)匣沼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)狰挡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人释涛,你說(shuō)我怎么就攤上這事加叁。” “怎么了唇撬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵它匕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我窖认,道長(zhǎng)豫柬,這世上最難降的妖魔是什么告希? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮烧给,結(jié)果婚禮上燕偶,老公的妹妹穿的比我還像新娘。我一直安慰自己础嫡,他們只是感情好指么,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著榴鼎,像睡著了一般伯诬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檬贰,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天姑廉,我揣著相機(jī)與錄音,去河邊找鬼翁涤。 笑死桥言,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葵礼。 我是一名探鬼主播号阿,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鸳粉!你這毒婦竟也來(lái)了扔涧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤届谈,失蹤者是張志新(化名)和其女友劉穎枯夜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體艰山,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡湖雹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙搬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔吏。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纵装,靈堂內(nèi)的尸體忽然破棺而出征讲,到底是詐尸還是另有隱情,我是刑警寧澤橡娄,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布诗箍,位于F島的核電站,受9級(jí)特大地震影響挽唉,放射性物質(zhì)發(fā)生泄漏扳还。R本人自食惡果不足惜才避,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氨距。 院中可真熱鬧,春花似錦棘劣、人聲如沸俏让。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)首昔。三九已至,卻和暖如春糙俗,著一層夾襖步出監(jiān)牢的瞬間勒奇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工巧骚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赊颠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓劈彪,卻偏偏與公主長(zhǎng)得像竣蹦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沧奴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容