在Swift中, 最常用的集合类型非数组莫属了. 然而相较于Objective-C, 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) }
除此之外, Swift为方便数组操作还添加了一系列高阶函数
.
* map
和 flatMap
— 如何对元素进行变换
-
filter
— 元素是否应该被包含在结果中 -
reduce
— 如何将元素合并到一个总和的值中 -
sequence
— 序列中下一个元素应该是什么? -
forEach
— 对于一个元素,应该执行怎样的操作 -
sort
,lexicographicCompare
和partition
— 两个元素应该以怎样的顺序进行排列 -
index
,first
和contains
— 元素是否符合某个条件 -
min
和max
— 两个元素中的最小/最大值是哪个 -
elementsEqual
和starts
— 两个元素是否相等 -
split
— 这个元素是否是一个分割符
这几个高阶函数
使用一个函数作为参数, 将重复切杂乱的的代码, 如遍历等操作给剥离出来. 同时, 函数的命名也是非常严谨且浅显的, 非常值得回味其中的奥妙.
不但如此, 利用Swift编译器的一些特性(待详细描述), 我们可以写出异常简洁直观的代码:
let numbers = Array.init(0..<10)
numbers.map { $0 * $0 }.filter{ $0 % 2 == 0 } // [0, 4, 16, 36, 64]
numbers.reduce(0, +) // 运算符也是函数, 所以可以这么写, 结果为 45
数组切片
Swift中还允许我们通过下标来获取特定范围内的元素:
let streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"]
let streetsSlice = streets[2 ..< streets.endIndex]
print(streetsSlice)
// Prints "["Channing", "Douglas", "Evarts"]"
let index = streetsSlice.index(of: "Evarts") // 4
print(streets[index!])
// Prints "Evarts"
这是通过对 subscript
进行重载实现的, RandomAccessCollection
协议的扩展中给出了方法的声明
public subscript(bounds: Range<Self.Index>) -> RandomAccessSlice<Self> { get }
需要注意的是, 在上面的代码事例中, 获取 streetsSlice
实例关于 Evarts
的 index
, 以及通过这个 index
获取的元素, 都是与开始的数组 streets
相匹配的. 所以, 可以猜测出 数组切片类型 ArraySlice
的子元素
, 只不过是原数组的引用, 并通过两个属性 来记录其相对于原数组的 首尾偏移.
值类型
与OC中的数组最大的不同, Swift中的 Array
是一个结构体类型, 本身具有不可变的特性, 即便我们可以使用 var
来声明一个可变的数组, 但是这个可变性只体现在变量本身上, 而不是指里面的值. 改变一个结构体变量的属性, 在概念上来说,和为整个变量赋值一个全新的结构体是等价的. 我们总是使用一个新的结构体, 并设置被改变的属性值, 然后用它替代原来的结构体.
而对于数组而言, 如果其中的元素是引用语义的实例(如类对象), 那么元素属性的变化 并不会对持有它的数组有什么影响, 然而如果元素是值语义的实例(值类型, 比如结构体), 那么当这个元素的属性发生改变时, 就会生成一个新的实例来替代该元素, 继而导致引用它的数组来生成一个新的实例数组代替.
举个例子:
struct Node {
var value: Int = 0;
}
class TestClass {
var nodes: [Node] = [] {
didSet {
print(nodes)
}
}
func changeFirstNode(to value: Int) {
if nodes.count > 0 {
nodes[0].value = value;
}
}
}
let test = TestClass();
// 创建一个nodes的数组赋值给test实例, 触发一次nodes的didSet
// 输出内容为[Node(value: 0), Node(value: 1), Node(value: 2)]
test.nodes = [Node(value: 0), Node(value: 1), Node(value: 2)]
// 更改了数组中第0个node的value值, 由于node是结构体类型, 当其变量变化时, 为nodes[0]这个变量赋值了一个全新的结构体
test.changeFirstNode(to: 5)
// 而由于Array类型也是值类型, 所以也会生成一个全新的数组, 将self.nodes指向这个心的数组, 从而导致出发了nodes的didSet.
// 此时会输出didSet中的打印: [Node(value: 5), Node(value: 1), Node(value: 2)]