集合是极大简化咱们工做效率的一种类型,咱们能够经过它存储同类型的多条数据(Array);能够存储同类型且惟一的多条数据(Set);也能够存储多条键值对(Dictionary)。学会高效的使用集合,不只能够提升咱们代码的性能,还能够极大地节约咱们的开发时间。swift
下面,让咱们开始吧!数组
在平常开发中,咱们会常常获取数组的元素,那么咱们应该如何获取集合的第一个元素呢?安全
能够经过如下三种方式:bash
arr[0]
arr[arr.startIndex]
first
经过下标:咱们只能应用于 Array ,对于 Set 或者 Dictionary 是不能用的,并且若是数组为空会 crash。多线程
经过index:能够应用于 Array 、 Set 、 Dictionary 。但数组为空也会 crash。app
经过 first
:能够应用于 Array 、 Set 、 Dictionary 。并且它返回的是一个可选值,若是集合为空不会 crash。async
结论:经过上面的对比,咱们知道应该使用 first
来安全的获取第一个元素。ide
let nums = Array(1...10)
if let first = nums.first {
print(first)
}
// 由于 Set 和Dictionary 是无序的,因此打印的第一个元素只是本次存储空间第一个位置的元素,屡次运行结果会不一致。
let set: Set = [1,2,3,4]
if let first = set.first {
print(first)
}
let dict = ["name": "fzh", "age": "18"]
if let first = dict.first {
print(first)
}
复制代码
说完了安全的获取第一个元素,下面咱们来看一下如何获取第二个元素(放心,不会有如何获取第三个元素啦)。性能
首先,下标的方式是确定不行的,咱们来看使用 index 的方式如何获取集合的第二个元素:优化
extension Collection {
var second: Element? {
//集合是否为空
guard self.startIndex != self.endIndex else { return nil}
//获取第二个元素的index
let index = self.index(after: self.startIndex)
//index 是否有效
guard index != self.endIndex else { return nil }
//返回第二个元素
return self[index]
}
}
复制代码
能够看出,上面的代码看着仍是比较多的。这种状况下,咱们可能会想到标准库是否有 second
属性?可是很抱歉,没有。那么咱们有简化上面的代码的方式吗?这个是能够有的。咱们能够经过切片(Slice)的方式来获取。
extension Collection {
var second: Element? {
// dropFirst():丢掉第一个元素 生成一个切片;再访问切片的第一个元素,也就是 second 了
return self.dropFirst().first
}
}
复制代码
切片是一个集合的子集。它主要用来进行一些临时的操做,好比获取连续的几个元素:
// 获取前半段的元素
var array = Array(1...8)
var firstHalf = array.dropLast(array.count/2)
print(firstHalf) // [1, 2, 3, 4]
复制代码
新建一个切片,并不会新建内存地址,将原集合的元素拷贝过来。它只是指向原集合的一个指针,所以建立一个slice 复杂度是O(1)。
并且,只要原集合没有改变,切片和集合的索引就是一致的。
print(array[2], firstHalf[2]) // 3 3
// 原集合改变,不影响已生成的切片
array[2] = -1
print(array[2], firstHalf[2]) // -1 3
复制代码
若是咱们将切片转换为数组的话,就会新建内存地址将切片范围中的元素拷贝到新的内存地址:
// 这一句会开辟内存,将值拷贝过来
let copy = Array(firstHalf)
print(copy) // [1, 2, 3, 4]
复制代码
咱们应该只在进行临时操做的时候使用切片,由于即便原集合的生命周期已经结束,切片仍是可能对原集合强引用。所以,长时间使用切片可能会致使内存泄漏。想详细了解切片的请移步此处。
咱们在使用切片时,要注意索引的问题,好比下面的代码就会 crash :
var array = Array(1...8)
let slice = array[2...5]
print(slice[0])
复制代码
对于上述例子,不要使用 slice[0]
来获取切片的第一个元素,由于咱们的切片的索引只包含[2...5]
,因此调用 slice[0]
会 crash。咱们能够将它转为数组,再用 [0]
获取第一个元素:
var subArr = Array(slice)
print(subArr[0]) // 3
复制代码
在咱们对集合进行运算的时候,咱们可能会写下面的代码:
let items = (1...4000).map { $0 * 2 }.filter { return $0 < 10 }
print(items.first) // Optional(2)
复制代码
虽然咱们可能须要的只是 items 的第一个元素,可是代码仍是会对集合的全部元素进行 map 和 filter 操做。
那么,咱们如何能只对想要的元素进行map 和 filter 操做呢?答案就是 lazy Function。咱们能够将上面的代码改成下面的样子:
let items = (1...4000).lazy.map { $0 * 2 }.filter { return $0 < 10 }
print(items.first) // Optional(2)
复制代码
下面是改变集合发生 crash 的一个例子:
var array = ["A", "B", "C", "D", "E"]
let index = array.firstIndex(of: "E")!
array.remove(at: array.startIndex)
print(array[index])
复制代码
由于在执行 remove 操做后,数组的长度发生了变化,而 index 刚好是原数组的最后一个元素,因此会数组越界。
咱们能够经过下面这种方式,安全的使用集合:
var array = ["A", "B", "C", "D", "E"]
array.remove(at: array.startIndex)
if let index = array.firstIndex(of: "E") {
print(array[index]) // E
}
复制代码
咱们只要记住一点就能够避免上面的问题:要在改变集合以后在获取 index 来获得数据
。
集合为了优化性能默认是单线程访问的,若是多条线程同时访问集合而又没有添加锁或者使用串行队列的话可能会发生出人意料的行为。
// thread 1
var sleepingBeears = [String]()
let queue = DispatchQueue.global()
// thread 2
queue.async {
sleepingBeears.append("Grandpa")
}
// thread 3
queue.async {
sleepingBeears.append("Cub")
}
// thread 4
print(sleepingBeears)
复制代码
上面的代码会产生以下的结果:
咱们可使用串行队列来避免上面的问题:
var sleepingBeears = [String]()
let queue = DispatchQueue(label: "Bear-Cave")
queue.async { sleepingBeears.append("Grandpa") }
queue.async { sleepingBeears.append("Cub") }
queue.async { print(sleepingBeears) }
//["Grandpa", "Cub"]
复制代码
因此,咱们在多线程操做集合的时候,要注意适当的时候添加锁来确保集合的正确性。
优势:
在咱们能提早预估使用集合的大概长度的时候,咱们能够经过如下的方式来建立固定容量的结合:
// Array reserveCapacity(_:)
var num = [Int]()
num.reserveCapacity(120)
for _ in 0...100 {
num.append(0)
}
//Set Set(minimumCapacity:)
var set = Set<Int>(minimumCapacity: 10)
//Dictionary Dictionary(minimumCapacity:)
var dict = Dictionary<String, String>(minimumCapacity: 10)
复制代码
优势:若是你知道要添加到集合中多少个元素,使用此方法能够避免屡次从新分配。
first
来访问集合的第一个元素好了,到这里这篇文章就结束了。但愿经过本篇文章让你们能在使用集合的时候更加驾轻就熟,更加安全高效。Happy Day!