这篇文章重点不是介绍Array Dictonary Set Range的简单使用的,文章重点是 Swift Collection中你可能不知道的东西,文章既有如何创建一个数组,也有ArraySlice(数组切片)的说明。之所以有一些很基础的东西,是看到很多Swift使用者在一知半解,在项目里写了很多怪异的写法,就算掌握了,跟着过一遍,也花不了几分钟。
Array : 数组
创建一个数组
- 创建一个空数组
let array1: Array<Int> = Array<Int>()
let array2: [Int] = []
- 创建带有初始值的数组
let array1 = [1, 2, 3] // [1, 2, 3, 4, 5]
let array2 = [Int](repeating: 0, count: 3) // [0, 0, 0]
可变性
在上边,我们声明数组是用let
关键字,也就是我们把数组声明成常量了,如果像下边这么写,会得到一个编译错误:
let array = [1, 2]
array.append(3)
error: cannot use mutating member on immutable value: 'array' is a 'let' constant
array.append(3)
如果需要改变数组中的元素,需要使用var
关键词定义:
var array = [1, 2]
array.append(3) //[1, 2, 3]
array.append(contentsOf: [4, 5]) //[1, 2, 3, 4, 5]
数组和标准库中的所有集合类型一样,是具有值语义的。当你创建一个新的数组变量并且把一个已经存在的数组赋值给它的时候,这个数组的内容会被复制。举个例子,在下面的代码中,x 将不会被更改:
var x = [1,2,3]
var y = x
y.append(4)
y // [1, 2, 3, 4]
x // [1, 2, 3]
var y = x 语句复制了 x,所以在将 4 添加到 y 末尾的时候,x 并不会发生改变,它的值依然是 [1,2,3]。当你把一个数组传递给一个函数时,会发生同样的事情;方法将得到这个数组的一份本地复制,所有对它的改变都不会影响调用者所持有的数组。
Array常用操作
添加删除元素
var array = [1, 2]
// 向数组添加一个元素
array.append(3) //[1, 2, 3]
// 拼接数组
array.append(contentsOf: [4, 5]) //[1, 2, 3, 4, 5]
// 插入元素
array.insert(6, at: array.endIndex) // [1, 2, 3, 4, 5, 6]
// 删除对应index的元素
array.remove(at: 5) // [1, 2, 3, 4, 5]
// 删除最后一个元素
array.removeLast() // [1, 2, 3, 4]
count & isEmtpy & forEach
let array = [1, 2]
// 数组元素个数
array.count // 2
// 数组是否为空
if array.isEmpty {
print("array is empty")
}
// 遍历数组
array.forEach {element in
print(element)
}
map & flatmap & filter & reduce
map & flatmap & filter & reduce 的用法,我之前写过,
点击这里
访问数组中元素
定义一个数组
var array = [1, 2, 3, 4, 5]
使用下标访问元素:
array[0] // 1
array[1] // 2
array[10] //超出长度,carsh
使用range operator访问范围内元素:
array[0...2] // [1, 2, 3]
array[0..<2] // [1, 2]
使用下标访问数组元素会有一个严重的问题,就是下标超出数组范围会carsh,说好的Swift更安全呢? 其实,使用下边访问数组元素是Swift不推荐的方法Swift 3 中传统的 C 风格的 for 循环被移除了,这是 Swift 不鼓励你去做索引计算的一个标志。手动计算和使用索引值往往可能带来很多潜在的 bug,所以最好避免这么做。
但是有些时候我们又不得不使用索引,为什么不在使用索引时提供可选值呢,比如:
let i = array[10]
i: Optional<Int> // 让取到的值是一个optional,这样可以避免崩溃
因为当你使用数组索引的时候,Swift默认你应该已经深思熟虑,对背后的索引计算逻辑进行过认真思考。不提供可选值。Swift默认你信任你的代码,在这个前提下,如果每次都要对获取的结果进行解包的话就显得多余了。而且如果提供可选值,一方面十分麻烦,每次都要解包,另一方面也是一个坏习惯。当强制解包变成一种习惯后,很可能你会不小心强制解包了本来不应该解包的东西。所以,为了避免这个行为变成习惯,数组根本没有给你可选值的选项。
如果你真的想要通过下标访问元素,同时还想防止崩溃的产生,可以通过给Array
进行拓展来实现这个功能:
extension Array {
subscript(safe idx: Int) -> Element? {
return idx < endIndex ? self[idx] : nil
}
}
取出数组元素的时候像这样:
array[safe: 100] // nil ,不会崩溃
array[safe: 1] //Optional<Int> ,需要解包
Swift 不鼓励你去做索引计算主要原因是我们可以使用数组切片。什么叫数组切片,下边再解释。先看在数组中,不使用下标直接访问元素,如何取到数组中的元素:
// 迭代数组
for x in array {
...
}
// 迭代除了第一个元素以外的数组其余部分
for x in array.dropFirst() {
...
}
// 迭代除了最后 5 个元素以外的数组
for x in array.dropLast(5) {
...
}
// 列举数组中的元素和对应的下标
for (num, element) in collection.enumerated() {
...
}
// 寻找一个指定元素的位置
if let idx = array.index { someMatchingLogic($0) } {
...
}
// 对数组中的所有元素进行变形
array.map { someTransformation($0) }
// 筛选出符合某个标准的元素
array.filter { someCriteria($0) }
ArraySlice : 数组切片
在上边的例子中,我们发现array.dropLast(5)
或者 array[0...2]
得到的并不是一个 Array<Int>
类型的数组,而是一个 ArraySlice
,ArraySlice
也就是数组切片,切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,只不过是用切片的方式来进行表示。这意味着原来的数组并不需要被复制。ArraySlice 具有的方法和 Array 上定义的方法是一致的,因此你可以把它们当做数组来进行处理。简单来说,就是Array
某一段内容的view
,它不真正保存数组的内容,只保存这个view
引用的数组的范围:
(图自:《Swift 进阶》)
图中,
fibs
表示一个斐波那契数列数组,即 [0, 1, 1, 2, 3, 5 ,...]
slice
表示fibs
的ArraySlice
fibs中包含了数组的长度
6
和一个指向 数组内存地址的指针prt
而
slice
包含了一个同样指向 数组内存地址的指针prt
,同时还有view
起始Index 1
,和结束Index6
,我们可以通过改变这个位置的起止区间,来表示fibs
这个数组的不同view
。
如果你需要将切片转换为数组的话,你可以通过将切片传递给 Array
的构建方法来完成:
Array(slice)
Array 与 NSArray
我们知道在Swift
中,Array
是按照值语义实现的,当我们这么写的时候,其实是拷贝了一份新的Array
:
var array1 = [0, 1, 2] // [0, 1, 2]
let array2 = array1 // [0, 1, 2]
// 对array1进行操作不会影响array2
array1.append(3) // array1: [0, 1, 2, 3] ; array2: [0, 1, 2]
// 因为array2 被用let 声明为常量,所以是不可变的
array2.append(3) // Compile error
Swift
对值类型的拷贝处理其实是使用copy on write
的方式,当你复制Array
时,真正的复制是不会发生的,两个数组同样引用同一个内存地址。只有当你修改了其中一个Array
的内容时,才会复制一份Array
并更改指针指向位置。
在Foundatioin
中,数组包括 NSArray
和 NSMutableArray
,
NSArray
和 NSMutableArray
都是类对象,数组是否可以被修改是通过NSArray
和 NSMutableArray
这两个类型来决定的,我们知道,在Objective-C中,复制它们执行的是引用语义,如果在Swift
中使用 NSArray
和 NSMutableArray
:会产生让人很奇怪的感觉:
let array = NSMutableArray(array: [1, 2, 3])
在上边的一句中:我们明明使用了 let
把 array
声明成一个常量,但是其实是可以通过:array.add(3)
来向 array
添加一个元素的,编译器并不会报错。
当我们用var
声明一个NSArray
时,我们希望他是可变数组,其实情况呢:
var mutableArray = NSArray(array: [1, 2, 3])
因为mutableArray
是 NSArray
类型的,你根本无法调用改变 mutableArray
元素的方法。
结论: 当我们使用 NSArray
和 NSMutableArray
时,Swift中的 var
和 let
只控制对应的变量是否可以被赋值成新的对象,并不能控制数组元素是否可以修改。
因为 NSArray
和 NSMutableArray
复制时执行的是引用语义,他们指向同一个内存地址,改变其中一个的元素时,另一个的也会改变:
let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1
array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3]
为了在使用 NSArray
对象时,执行值语义,我们必须使用它的copy
方法复制所有的元素:
let array1 = NSMutableArray(array: [1, 2, 3])
let array2: NSArray = array1
let deepCopyArray = array1.copy() as! NSArray
array1.insert(0, at: 0) // array1:[0, 1, 2, 3] ;array2:[0, 1, 2, 3] ; deepCopyArray:[1, 2, 3]