swift的Array中ArraySlice的源码解析

1,060 阅读2分钟

今天无聊写 swift 的时候,我写了如下代码

let entrity = [1, 2, 3, 4, 5, 6]
var slice = entrity[1...3]
slice[0] = 10

最终我发现居然崩溃了,我就纳闷了,按照我的理解,这应该是个新的数组,虽然我知道它是 ArraySlice,官方对此也只是做了解释,今天我们来分析下源码

这里声明下 Array 的 buffer 对象是 ContiguousArrayBuffer

Array 源码

 @inlinable
  public subscript(bounds: Range<Int>) -> ArraySlice<Element> {
    get {
      _checkIndex(bounds.lowerBound)
      _checkIndex(bounds.upperBound)
      return ArraySlice(_buffer: _buffer[bounds])
    }
    set(rhs) {
      _checkIndex(bounds.lowerBound)
      _checkIndex(bounds.upperBound)
      // If the replacement buffer has same identity, and the ranges match,
      // then this was a pinned in-place modification, nothing further needed.
      if self[bounds]._buffer.identity != rhs._buffer.identity
      || bounds != rhs.startIndex..<rhs.endIndex {
        self.replaceSubrange(bounds, with: rhs)
      }
    }
  }

buffer[bounds]肯定调用了 subscript 方法,我们去看看这里发生了什么

ContiguousArrayBuffer 源码

@inlinable
  internal subscript(bounds: Range<Int>) -> _SliceBuffer<Element> {
    get {
      return _SliceBuffer(
        owner: _storage,
        subscriptBaseAddress: firstElementAddress,
        indices: bounds,
        hasNativeBuffer: true)
    }
    set {
      fatalError("not implemented")
    }
  }

这里生成了一个 SliceBuffer 对象用于 ArraySlice 的初始化,也就说 ArraySlice 的 buffer 其实就是 SliceBuffer.接下来我们继续回到 ArraySlice 的下标读取方法中

ArraySlice 源码

@inlinable
  public subscript(index: Int) -> Element {
    get {
      // This call may be hoisted or eliminated by the optimizer.  If
      // there is an inout violation, this value may be stale so needs to be
      // checked again below.
      let wasNativeTypeChecked = _hoistableIsNativeTypeChecked()

      // Make sure the index is in range and wasNativeTypeChecked is
      // still valid.
      let token = _checkSubscript(
        index, wasNativeTypeChecked: wasNativeTypeChecked)

      return _getElement(
        index, wasNativeTypeChecked: wasNativeTypeChecked,
        matchingSubscriptCheck: token)
    }
    _modify {
      _makeMutableAndUnique() // makes the array native, too
      _checkSubscript_native(index)
      let address = _buffer.subscriptBaseAddress + index
      yield &address.pointee
      _endMutation();
    }
  }

看到这里,我们大家应该都知道要看_getElement 这个方法了 我们再看看这个干了什么

@inline(__always)
  public // @testable
  func _getElement(
    _ index: Int,
    wasNativeTypeChecked: Bool,
    matchingSubscriptCheck: _DependenceToken
  ) -> Element {
#if false
    return _buffer.getElement(index, wasNativeTypeChecked: wasNativeTypeChecked)
#else
    return _buffer.getElement(index)
#endif
  }

咦,我们一看只是很单纯的 getElement,我们继续点进去看看

SliceBuffer 源码

@inlinable
  internal func getElement(_ i: Int) -> Element {
    _internalInvariant(i >= startIndex, "slice index is out of range (before startIndex)")
    _internalInvariant(i < endIndex, "slice index is out of range")
    return subscriptBaseAddress[i]
  }

好家伙,原来问题出在这里

总结

看到这里大家应该都明白了,SliceBuffer 初始化的时候有传 bounds 给它,它直接把 startIndex 设置成了 bounds 的 startIndex,所有咱们一开始写的代码直接取下标为 0 的时候,就直接崩溃了