Array

Array 是有序的,支持随机读写的集合类型

@frozen struct Array<Element>

1. 基本操作

1.1 初始化,构造

// 创建空的Array, 类型推断

let array1 = [Int]()

var array2 = Array<Int>()


// 创建空的Array, 指定类型

var array3 : [Int] = []

// 通过列表初始化

var array4 = [1,2,3,4,5]

// 调用init方法初始化

var array5 = Array(repeating:0, count:3)

1.2 增加元素

+append(_:) 可以在数组尾部添加元素

insert(_:,at:) 在索引指定的位置插入元素

array1.append(11)
array1 += [13,12]

array1.insert(13,at:0)

1.3 删除元素

remove(at:) 删除索引指定位置的元素

removeLast() 删除数组中的最后一项

// 
array1.remove(at:5)

// Can't remove last element from an empty collection: 
array1.removeLast()

1.4 访问数组

使用下标法访问Array中的元素

array1[0]

array1[1] = 12

// 下标法可以使用区间运算符
array1[1...3] = [2,3,4]

1.5 遍历数组

可以直接使用for-in循环遍历数组

for value in array1 {
    print("\(value)")
}

如果同时需要每个数据项的值和索引值,可以使用 enumerated() 方法来进行数组遍历。enumerated() 返回一个由索引值和数据值组成的元组数组。

for (index, value) in shoppingList.enumerated() {
    print("Item \(String(index + 1)): \(value)")
}

1.6 数组越界问题

如果你试图通过越界索引来执行访问或者修改数据的操作,会引发一个运行时错误。此时可以使用索引值和数组的 count 属性进行比较来在使用该索引之前检验其是否有效。除了当 count 等于 0 时(说明这是个空数组),最大索引值一直是 count - 1,因为数组都是零起索引。

2. 数组自动扩容

每个数组都会预留一定数量的内存空间去保存元素。当在数组中添加元素且超出预留的数量;数组会申请一块更大的内存空间,并将元素复制到新的内存空间中。

新的内存空间是旧空间的数倍大小。指数级的增长策略意味着多次append操作的平均性能是实时的。触发内存充分配的append 操作有一定的性能消耗,但是数组的容量的逐渐增大,内存分配将会发生的更少。

如果预先知道需要存储的元素数量,可以在添加元素之前调用reserveCapacity(_:) 避免内存重分配。capacitycount 属性可以用于计算可用容量。

对于大部分元素类型,存储区域是一个连续的内存空间。对于元素类型是 class 或者 @objc protocol 的数组,存储区域可以是一个连续的内存空间或者NSArray的对象。

3. 修改Array 的拷贝

Array 是struct类型,也就是值类型。Array对象的每一个拷贝之间都是独立的,修改一个拷贝并不会影响其他拷贝。

如果 Array 的元素类型是值类型,那不仅是Array对象的每个拷贝是独立的,Array对象的每个拷贝对应的元素也是独立的,修改某个拷贝的元素不会影响其他拷贝的对应元素。

var numbers = [1, 2, 3, 4, 5]
var numbersCopy = numbers
numbers[0] = 100
print(numbers)
// Prints "[100, 2, 3, 4, 5]"
print(numbersCopy)
// Prints "[1, 2, 3, 4, 5]"

如果 Array 的元素类型是引用类型,尽管Array对象的每个拷贝是独立的,Array对象的每个拷贝对应的元素是浅拷贝,指向同一个引用类型对象。

// An integer type with reference semantics
class IntegerReference {
    var value = 10
}
var firstIntegers = [IntegerReference(), IntegerReference()]
var secondIntegers = firstIntegers

// Modifications to an instance are visible from either array
firstIntegers[0].value = 100
print(secondIntegers[0].value)
// Prints "100"

// Replacements, additions, and removals are still visible
// only in the modified array
firstIntegers[0] = IntegerReference()
print(firstIntegers[0].value)
// Prints "10"
print(secondIntegers[0].value)
// Prints "100"

标准库中的许多集合包括Array都使用了 写时拷贝 优化。 Array 的多个拷贝共享同一块存储空间,直到某一个拷贝被修改。

当Array的某个拷贝首次被修改时,会复制出一块新的独属于该拷贝的存储空间,然后再修改;之后再修改就不再有申请新的存储空间的开销了。

var numbers = [1, 2, 3, 4, 5]
var firstCopy = numbers
var secondCopy = numbers

// The storage for 'numbers' is copied here
numbers[0] = 100
numbers[1] = 200
numbers[2] = 300
// 'numbers' is [100, 200, 300, 4, 5]
// 'firstCopy' and 'secondCopy' are [1, 2, 3, 4, 5]

4. Array 和 NSArray 之间桥接

当你需要 Array 和 NSArray 对象之间相关转换,可以使用类型声明运算符(as)来实现桥接。桥接的前提是元素的类型必须是class或者@objc protocol或者Foundation类型

有以下的示例:

colorsArray<String>的数组,元素类型为 String, String 是一个Foundation类型,可以桥接;

moreColorsArray<String?>, 元素类型为 String?, String? 本质上是枚举类型,不是class或者@objc protocol或者Foundation类型中任意一种,因此不可以桥接。


let colors = ["periwinkle", "rose", "moss"]
let moreColors: [String?] = ["ochre", "pine"]

let url = URL(fileURLWithPath: "names.plist")
(colors as NSArray).write(to: url, atomically: true)
// true

(moreColors as NSArray).write(to: url, atomically: true)
// error: cannot convert value of type '[String?]' to type 'NSArray'

4.1 class 或者 @objc protocol 的桥接性能

当元素类型为 class 或者 @objc protocol, Array 桥接为 NSArray的时间复杂度和空间复杂度都是 O(1)

当元素类型为 class 或者 @objc protocol, NSArray 桥接为 Array 首先调用 copy(with:) 方法获得一个可变的拷贝数组,然后执行额外的Swift保存操作,将会消耗O(1)的时间复杂度。如果是 NSMutableArray 对象桥接为Arraycopy(with:)通常会在O(1)时间内返回同一个数组对象,否则copy的性能将是不确定的。如果copy(with:)返回同一个数组对象,NSArrayArray 共享同一块支持写实拷贝的存储空间。

4.2 Foundation 中非class类型的桥接性能

当元素类型为Foundation中的非class类型, Array 桥接为 NSArray的时间复杂度和空间复杂度都是 O(n)

当元素类型是Foundation中的非class类型,NSArray 桥接为 Array会在 O(n) 的时间复杂度内将元素拷贝到一块连续存储中。

最后更新于