array预防越界 swift

2014-11-13 23:36:26 NapoleonBai 阅读数 2202

一天一点,好好学习

前进就是动力,动力推动前进

数组:

数组使用有序列表存储同一类型的多个值,相同的值可以多次出现在同一个数组的不同位置.

Swift数组特定于它所存储元素的类型.这与OC的NSArray和NSMutableArray不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特定信息.在swift中,数据值在呗存储进入某个数组之前必须明确,方法是通过显示的类型标注或类型推断,而且不是必须是Class类型.

eg:

我们创建了一个Int类型的数组,那么我们就不能往里面存储其他类型的数据.

因此,Swift中的数组是类型安全的,并且它们包含的类型必须明确.

好啦:口水话也不多说了,看Code明确一切:走起

//
//  ViewController.swift
//  SwiftArrayAndDictionary
//
//  Created by NapoleonBai on 14-11-13.
//  Copyright (c) 2014年 NapoleonBai. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.managerArray()
    }
    /**
    此函数用来管理数组
    */
    func managerArray(){
        //定义一个数组对象
        //1.不指定参数类型
        var array = ["曹操","刘备","孙权"]
        
        println("array 一共有 \(array.count) 个item")   //array 一共有 3 个item
        
        //2.指定数组类型
        var arrayTwo : [String] = ["魏","蜀","吴"]
        //如果采用 String[] ,这里会抛出error,这种做法是在开始的beta版允许
        
        if arrayTwo.isEmpty{
            //这是数组自带的方法,用来判断是否为空
            println("arrayTwo 为空,没有元素可取")

        }else{
            println("arrayTwo 第一个选项为: \(arrayTwo[0])")  //arrayTwo 第一个选项为: 魏
        }
        
        //这里的取值方式同多种语言一样,都是根据下标获取
        
        //3.创建空数组
        var arrayThree = [Int] ()
        
        //使用append添加数组元素
        arrayThree.append(999)
        
        arrayThree.append(888)
        
        
        println("arrayThree 的值为 \(arrayThree)") //arrayThree 的值为 [999, 888]
        
        //4.创建指定大小的数组,并赋值
        var arrayDoubles = [Double](count: 5, repeatedValue: 0.0)
        
        println("the arrayDoubles = \(arrayDoubles)")   //the arrayDoubles = [0.0, 0.0, 0.0, 0.0, 0.0]
        
        //so,我们怎样拼接两个数组呢?那么请看
        // 通过类型自动推测,不用指定特定类型
        var arrayOtherDoubles = Array(count: 3, repeatedValue: 99.9)
        
        // 两个数组相加,新数组的类型会根据这两个数组的类型推断出来
        // 特别注意,一般只支持同类型的数组相拼接
        var addDoubles = arrayDoubles + arrayOtherDoubles
        
        println("addDoubles = \(addDoubles)")   //addDoubles = [0.0, 0.0, 0.0, 0.0, 0.0, 99.9, 99.9, 99.9]
        
        //当然,如果
        //eg:
        var arrayTest = Array(count:2,repeatedValue:"哈哈")
        
        //var arrarAddTest = arrayDoubles + arrayTest  //error: Cannot invoke '+' with an argument list of type '(@lvalue [(Double)],@lvalue Array<String>)'
        
        var arrayTestTwo = [Int](count: 2, repeatedValue: 33)
        
       //var arrayAddTest = arrayDoubles + arrayTestTwo //error:同上种类型类似
        
        
        //继续添加arrayTwo数组中
        arrayTwo.append("三国魏")
        
        arrayTwo.append("三国蜀")
        
        arrayTwo.append("三国吴")
        //这样的话,arrayTwo中有六个元素了
        
        println("the arrayTwo = \(arrayTwo)")   //改变前:the arrayTwo = [魏, 蜀, 吴, 三国魏, 三国蜀, 三国吴]
        
        //如果我们想更换其中的元素,那么我们应该怎么做呢?
        
        //arrayTwo[6]="三国蜀黍" // fatal error: Array index out of range
        //println("the arrayTwo[6] = \(arrayTwo[6])")
        //下标越界了,不好意思,一下子就忘了哈,那如果我们遇到了该怎么处理呢?
        //先暂时保留这个问题哈
        
        arrayTwo[5] = "三国蜀黍"
        
        println("the arrayTwo = \(arrayTwo)")   //改变后:the arrayTwo = [魏, 蜀, 吴, 三国魏, 三国蜀, 三国吴]

        //如果,我们需要在某个值之间插入某个值了,怎么办?
        //下面我们就在数组第一个位置插入'小白杨'
        arrayTwo.insert("小白杨", atIndex: 0)
        
        println("the arrayTwo = \(arrayTwo)")   //the arrayTwo = [小白杨, 魏, 蜀, 吴, 三国魏, 三国蜀, 三国蜀黍]

        //然后我们发现,我们insert错了数组,于是乎,就需要删除这个值,是不是?
        //好吧,我们怎么删除呢?
        //eg:
        arrayTwo.removeAtIndex(0)
        
        println("remove index=0 item after ,the array = \(arrayTwo)")   //remove index=0 item after ,the array = [魏, 蜀, 吴, 三国魏, 三国蜀, 三国蜀黍]
        
        //看到了吧果然删除了,当然了,删除还有
        //arrayTwo.removeLast()         //移除最后一个元素
        //arrayTwo.removeAll(keepCapacity: true)    //移除所有元素
        
        //到这里,数组就差不多讲解完毕了
        //当然,上面提到一个问题,就是数组越界的问题,那么我们怎么处理呢?
        //这就很简单了嘛,判断一下下就OK了呀...嘻嘻
     
        //对了,搬回来一下下,还有很强大的数组遍历还没有提到,是不是?
        //嘻嘻,继续
        
        for tempData in arrayTwo{
            println("the tempData=\(tempData)")
            /*
            the tempData=魏
            the tempData=蜀
            the tempData=吴
            the tempData=三国魏
            the tempData=三国蜀
            the tempData=三国蜀黍
            */
        }
        //其实在前面已经说过了,只是本篇没有而已
        //为什么不需要var tempData呢?这个就是for遍历提供的方便了,in 前面的自动设置为临时变量,我们只需要负责命名即可
        //那还有没有其他遍历方式呢?请看下面
        
        for (index, value) in enumerate(arrayTwo) {
            println("第 \(index + 1) 个元素: \(value)")
            /*
            第 1 个元素: 魏
            第 2 个元素: 蜀
            第 3 个元素: 吴
            第 4 个元素: 三国魏
            第 5 个元素: 三国蜀
            第 6 个元素: 三国蜀黍
            */
        }
        /*
        这是使用全局enumerate函数来进行数组遍历,因为enumerate返回一个由每一个数据项索引值和数据值组成的元组,即可得到索引值和值,根据个人需要吧
        当然,还有更多的方式,就靠自己去挖掘了...
        开挖掘机去...嘻嘻
        那么,问题来了...
        */
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

今天的注释有点稀疏,因为想把文字多提出来写,但是发现时间又不是很多.

吃掉晚饭已是九点多,然后看点其他的东东,再研究一下这个,就很晚了... ... 

所以很多能节省就节省了...嘻嘻

不过,有什么不清楚的,可以提出来,大家一起研究研究,毕竟,我也刚开始学.

当然了,这里的code,抛开了beta版的使用方式,所以,勿怪

时间不早了,洗洗睡吧

当然,不得不多提一句:

如果用var 定义的数组,那么可以手动添加元素,比如append,insert这样,并且也可以修改:array[0]="update"

如果用let定义的数组,那么就不能了.也类似于可变和不可变之分吧


2018-05-11 09:00:27 u012581760 阅读数 919

swift 避免删除数组越界,可以从Index最大的数值删除

 var sortElementArray = [1,2,3,4,5,6,7,8]
        var deleteArray = [0,7,4]
        deleteArray = deleteArray.sorted(by: {$0 > $1})
        print(deleteArray)
        for (_,element) in deleteArray.enumerated() {
            sortElementArray.remove(at: element)
        }
        print(sortElementArray)
打印结果:
[7, 4, 0]
[2, 3, 4, 6, 7]

上面这段代码要删除sortElementArray中index为0,7,4所在的数值即删除sortElementArray中1,5,8那么可以先把删除的数组按从大到小的顺序排列,
然后在删除sortElementArray

如果没有对deleteArray进行排序直接删除的话,报如下错误
这里写图片描述

因为删除了1之后,sortElementArray中只有7个元素,那么index为7的话就越界了,所以要先对删除的index进行从大到小的排序,这样大的位置删除了,小的index的位置是不会变的.

2016-07-15 14:16:47 liuwin7 阅读数 979

1.相遇

有个业务需求,把表格中的数据整合到一个数组中,如下

Name Age Item1 Item2 Item3 Country
Jack 21 32 43 null China
Tom 23 83 67 75 US

整合后的结果是这样

[
    {
        "Name": "Jack",
        "Age": 21,
        "score": [
            {
                "Item1": 32
            },
            {
                "Item2": 43
            }
        ],
        "Country": "China"
    },
    {
        "Name": "Tom",
        "Age": 23,
        "score": [
            {
                "Item1": 83
            },
            {
                "Item2": 67
            },
            {
                "Item3": 75
            }
        ],
        "Country": "US"
    }
]

有个中间的状态,就是把一条记录整合出来之后,取中间的几个元素作为一个整体,所以需要把中间的几个数据切片到一个新的数组中。
之前在学习Swift的时候,了解过一下数组的用法,以为是非常了解了,实则不然。

2.对于切片数组的 subscript ,你了解多少?

一个简单例子

let array = ["A", "B", "C", "D"]
let slicedArray = array[1..<3]
print(slicedArray.count) // 2
print(slicedArray[0]) // error

WHY?

明明数组中有两个元素,但是为什么用subscript访问,却报错呢?
还有

let slicedArray String = slicedArray.reduce("start: ") { (acc, item) -> String in
    "\(acc)\(item)"
}
print(slicedString) // start: BC

很明显这个”数组”中是有数据的呀,怎么就是取不出来呢?

3.startIndex & endIndex

slicedArray 的 startIndex 和 endIndex 是多少呢?

print(slicedArray.startIndex) // 1 instead 0
print(slicedArray.endIndex) // 3

所以,在用[0]访问slicedArray的时候,其实是越界的 out of bounds !!!!

4.怎么解决这个问题

不去详细的解释为什么,这个跟swift对数组的设计理念相关,copy-on-write,目的之一提高性能
两个方案解决这个问题:

  1. 违背swift的设计原则
    直接创建一个新的数组变量

    let lastedArray = Array(array[1..<3])
    print(lastedArray.startIndex) // 0
    print(lastedArray.endIndex) // 2
    print(lastedArray[0]) // B
  2. 使用数组的属性startIndex,endIndex

    combinedArray[combinedArray.startIndex] // B
    combinedArray[combinedArray.startIndex.advancedBy(1)] // C

5.参考文献

  1. swift-subarrays-array-and-arrayslice
  2. swift-array-and-arrayslice-xcode-7.2
2019-06-07 05:28:18 liyan223 阅读数 308

Swift是一种注重安全的语言, 对于一个编程语言,在访问数组的元素的时候,通常要检查index是否超过数组的长度,防止越界的访问数组。 代码 如下

let list = [1, 2, 3, 4]

if index >= 0 && index < list.count {
    print(list[index])
}

有时候,优秀的编程语言会提供便利的方法来处理上面的情况,Swift可以利用扩展(extension)的方式来添加一个安全的访问方法

public extension Collection {
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

这样, 我们就可以放心的访问数组,而不用担心越界问题。

let list = [1, 2, 3, 4]
let index = 5

let safeIndexValue = list[safe: 5] // nil, index超过数组下标,安全返回nil
let value1 = list[1] // 2

本次使用的Swift语法是基于5.0.1版本, Xcode 10.2.1

2018-06-27 09:13:31 weixin_33763244 阅读数 258

关于 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-CNSArray
最主要的初始化函数,是通过 _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>
复制代码

从调用的情况来看,本质上还是调用了 _ContiguousArrayBufferfirstElementAddress

但是在创建时,会有类型检查。

_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 。 下面是对数组中一些批量操作的总结:

  • removeAllinsert<C>(contentsOf: C, at: Int)removeSubrange:最终调用的是 replaceSubrange
  • append<S : Sequence>(contentsOf newElements: S)init(repeating repeatedValue: Element, count: Int):最终都是操作内存,循环初始化新的内存空间和值。

有什么不正确的地方,欢迎指出。