Swift进阶 - 更高效的使用集合

集合是极大简化咱们工做效率的一种类型,咱们能够经过它存储同类型的多条数据(Array);能够存储同类型且惟一的多条数据(Set);也能够存储多条键值对(Dictionary)。学会高效的使用集合,不只能够提升咱们代码的性能,还能够极大地节约咱们的开发时间。swift

下面,让咱们开始吧!数组

获取集合的元素

获取第一个元素

在平常开发中,咱们会常常获取数组的元素,那么咱们应该如何获取集合的第一个元素呢?安全

能够经过如下三种方式:bash

  • 经过下标:arr[0]
  • 经过index: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
    }
}
复制代码

切片(Slice)

切片是一个集合的子集。它主要用来进行一些临时的操做,好比获取连续的几个元素:

// 获取前半段的元素
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]
复制代码

咱们应该只在进行临时操做的时候使用切片,由于即便原集合的生命周期已经结束,切片仍是可能对原集合强引用。所以,长时间使用切片可能会致使内存泄漏。想详细了解切片的请移步此处

Note

咱们在使用切片时,要注意索引的问题,好比下面的代码就会 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
复制代码

Lazy Function

在咱们对集合进行运算的时候,咱们可能会写下面的代码:

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

是否在改变集合

下面是改变集合发生 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"]
复制代码

因此,咱们在多线程操做集合的时候,要注意适当的时候添加锁来确保集合的正确性。

尽可能使用不可变集合

优势:

  • 可读性更高
  • bug更少
  • 可使用切片和lazy来进行可变集合的操做
  • 编译器性能更高

在咱们能提早预估使用集合的大概长度的时候,咱们能够经过如下的方式来建立固定容量的结合:

// 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来访问集合的第一个元素
  • 切片是对原集合的强引用,不要长时间使用它,可能会形成内存泄漏
  • 咱们在进行大量的链式运算并且只获取结果的部分数据时,咱们可使用 lazy functions
  • 要在改变集合以后在获取 index 来获得数据
  • 在多线程操做集合的时候,要注意适当的时候添加锁来确保集合的正确性

好了,到这里这篇文章就结束了。但愿经过本篇文章让你们能在使用集合的时候更加驾轻就熟,更加安全高效。Happy Day!

参考

相关文章
相关标签/搜索