集合是極大簡化咱們工做效率的一種類型,咱們能夠經過它存儲同類型的多條數據(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!