Swift 解讀 - Collection 大家族(上篇)

概要

集合類(lèi)型對(duì)任何一門(mén)現(xiàn)代化編程語(yǔ)言都至關(guān)重要,它們?cè)谠S多可見(jiàn)或者不可見(jiàn)的地方,影響著代碼質(zhì)量與執(zhí)行效率。Swift 在集合類(lèi)型的設(shè)計(jì)和實(shí)現(xiàn)上,進(jìn)行了諸多的考量,讓它兼具易用性、高性能以及擴(kuò)展性,但同時(shí)也增加了集合的復(fù)雜性。很多時(shí)候我們都在說(shuō) Swift 會(huì)比 OC 更快,但它到底快在哪里呢?對(duì)于名 Swifter 來(lái)說(shuō),單純的數(shù)據(jù)統(tǒng)計(jì)分析的結(jié)果無(wú)說(shuō)服力的,我們要知其然,同時(shí)更要知其所以然,那么本系列文章將圍繞著 Swift 是如何實(shí)現(xiàn)高性能的集合類(lèi)型來(lái)展開(kāi)。

起點(diǎn) Sequence

在 Swift 中,集合是一個(gè)復(fù)雜的類(lèi)型家族,是由許多 Protocol 組成的,其中 Sequence 是 Swift 整個(gè)集合類(lèi)型體系中的起點(diǎn),它抽象了一個(gè)集合最原始的協(xié)議。僅表示一系列類(lèi)型相同的元素,而不對(duì)這一系列元素的性質(zhì)有任何額外的約定。它只約定一個(gè)動(dòng)作,就是可以從序列的當(dāng)前位置讀取下一個(gè)元素,也即表示序列需要對(duì)外提供一個(gè)迭代器來(lái)實(shí)現(xiàn)對(duì)外的訪(fǎng)問(wèn)。

Sequence 的實(shí)現(xiàn)

我們可以在 Sequence.swift 文件中查看到 Sequence 協(xié)議的實(shí)現(xiàn)方法(在前言篇中在已經(jīng)講述過(guò)如何編譯和查看 Swift 的源碼,系列文章將不作重復(fù)說(shuō)明):

public protocol Sequence {
  associatedtype Element
  associatedtype Iterator : IteratorProtocol where Iterator.Element == Element
  __consuming func makeIterator() -> Iterator    
}

Sequence 協(xié)議中定義了大量的輔助函數(shù),大部分都已通過(guò) Protocol Extension 實(shí)現(xiàn)了,不一一列舉,這里的事例代碼只保留了 Sequence 協(xié)議最核心代碼。從上面源碼可以看出,序列的實(shí)現(xiàn)思路非常簡(jiǎn)單:

  • Element:表示序列中元素的類(lèi)型;
  • Iterator:表示 IteratorProtocol 并且 Iterator 的 Element 與序列的 Element 相同;
  • makeIterator():返回一個(gè) Iterator,Iterator 迭代器提供了訪(fǎng)問(wèn)序列中下一個(gè)元素的能力,它的具體實(shí)現(xiàn)如下:
public protocol IteratorProtocol {
  associatedtype Element
  mutating func next() -> Element?
}
  • Element:表示 next() 返回的元素類(lèi)型,如上面所述,Element 的類(lèi)型與相應(yīng)的序列的 Element 保持一致;
  • next():方法是問(wèn)序列下一個(gè)元素的接口,如果沒(méi)有下一個(gè)元素則返回 nil;

IteratorProtocol 協(xié)議與 Sequence 協(xié)議是一對(duì)緊密相連的協(xié)議。序列通過(guò)創(chuàng)建一個(gè)提供對(duì)其元素進(jìn)行訪(fǎng)問(wèn)的迭代器,它通過(guò)跟蹤迭代過(guò)程并在調(diào)用 next() 時(shí)返回一個(gè)元素。

在我們使用 for-in 來(lái)訪(fǎng)問(wèn)序列或者集合時(shí), Swift 實(shí)質(zhì)在底層通過(guò)迭代器來(lái)循環(huán)遍歷數(shù)組,正常我們通過(guò) for-in 來(lái)遍歷一個(gè)數(shù)組的用法:

let colors = ["red", "white", "black"]
for color in colors {
    print(color)
}

實(shí)質(zhì)上 Swift 在底層通過(guò) Sequence 的 Iterator 能力,做了如下轉(zhuǎn)換:

let colors = ["red", "white", "black"]
var iterator = colors.makeIterator()
while let color = iterator.next() {
    print(color)
}

這兩種方式訪(fǎng)問(wèn)序列的效果是等同的,只是一種簡(jiǎn)單的語(yǔ)法轉(zhuǎn)換,所以所有基于 Sequence 的協(xié)議,除了可以通過(guò)過(guò) for 來(lái)訪(fǎng)問(wèn)元素外,還可以通過(guò)迭代器來(lái)序列元素。

自定義序列

我了解 Sequence 的具體實(shí)現(xiàn)后,我們來(lái)嘗試實(shí)現(xiàn)自己的序列,下面我們來(lái)實(shí)例一個(gè)輸出 1…n 的平方數(shù)的序列。

首先我們需要先實(shí)現(xiàn)一個(gè) SquareIterator 的迭代器來(lái)遍歷平方數(shù)序列:

struct SquareIterator: IteratorProtocol {
    typealias Element = Int
    var state = (curr: 0, next: 1)
    mutating func next() -> SquareIterator.Element? {
        let curr = state.curr
        let next = state.next
        state = (curr: next, next: next + 1)
        if curr == 0 {
            return 1
        }
        return curr * curr
    }
}

通過(guò)在迭代器中定義一個(gè) state 來(lái)記錄當(dāng)前迭代過(guò)程的狀態(tài)信息,實(shí)現(xiàn)了該迭代器后,我們就需要實(shí)現(xiàn) Square 序列的 Sequence 協(xié)議:

struct Square: Sequence {
    typealias Element = Int
    func makeIterator() -> SquareIterator {
        return SquareIterator()
    }
}

通過(guò)實(shí)現(xiàn)了 SequenceIteratorProtocol 兩個(gè)協(xié)議,那么一個(gè)簡(jiǎn)單的 Square 序列即開(kāi)發(fā)完畢,我們可以嘗試去執(zhí)行它:

let square = Square()
var iterator = square.makeIterator()
while let num = iterator.next(), num <= 100 {
    print(num) // 1,1,3,9,16,25,36,49,64,81,100
}

到這里我們已經(jīng)完成一個(gè)自定義的序列,它支持通過(guò)迭代器來(lái)遍歷序列的所有元素,但是沒(méi)有辦法通過(guò)下標(biāo)的方式來(lái)訪(fǎng)問(wèn)序列元素,至于如何實(shí)現(xiàn)下標(biāo)訪(fǎng)問(wèn),這其中又涉及到另一個(gè)協(xié)議:Collection,接下來(lái)我們就來(lái)談?wù)?Collection Protocol。

Collection

Collection 是一個(gè)繼承于 Sequence 序列,是一個(gè)元素可以反復(fù)遍歷并且可以通過(guò)索引的下標(biāo)訪(fǎng)問(wèn)的有限集合。集合在標(biāo)準(zhǔn)庫(kù)中廣泛使用,當(dāng)我們?cè)谑褂脭?shù)組、字典和其他集合時(shí),大多將受益于 Collection 協(xié)議聲明和實(shí)現(xiàn)的操作。 除了集合從 Sequence 協(xié)議繼承的操作之外,最大的不同點(diǎn)是可以訪(fǎng)問(wèn)依賴(lài)于訪(fǎng)問(wèn)集合中特定位置的元素的方法。

Collection 的實(shí)現(xiàn)

源碼

我們可以在 Collection.swift 查看到 Collection 的具體實(shí)現(xiàn):

public protocol Collection: Sequence {
  override associatedtype Element
  associatedtype Index : Comparable
  var startIndex: Index { get }
  var endIndex: Index { get }
  associatedtype Iterator = IndexingIterator<Self>
  override __consuming func makeIterator() -> Iterator

  associatedtype SubSequence: Collection = Slice<Self>
  where SubSequence.Index == Index,
        Element == SubSequence.Element,
        SubSequence.SubSequence == SubSequence
  
  @_borrowed
  subscript(position: Index) -> Element { get }
  subscript(bounds: Range<Index>) -> SubSequence { get }

  associatedtype Indices : Collection = DefaultIndices<Self>
    where Indices.Element == Index, 
          Indices.Index == Index,
          Indices.SubSequence == Indices
       
  var indices: Indices { get }
}
  • Element、makeIterator:重寫(xiě) Sequence 的 Element、makeIterator;
  • startIndex、endIndex:非空集合中第一個(gè)、最后一個(gè)元素的位置;
  • subscript:下標(biāo)訪(fǎng)問(wèn)集合元素,例如 collection[i]、collection[0...i];
  • indices: 集合的索引

通過(guò)源碼,我們可以發(fā)現(xiàn) CollectionSequence 最大的不同點(diǎn)是提供了索引能力,以此基礎(chǔ)上提供了通過(guò)下標(biāo)訪(fǎng)問(wèn)元素的能力。 Collection 的自定義了迭代器:IndexingIterator,接下來(lái)我們了解下 What is IndexingIterator。

IndexingIterator

Collection 的迭代器使用 IndexingIterator,從名字的字面意思來(lái)看,這就是與下標(biāo)位置有關(guān)的迭代器,我們先來(lái)看下它的具體實(shí)現(xiàn):

public struct IndexingIterator<Elements : Collection> {
  internal let _elements: Elements
  internal var _position: Elements.Index
  
  init(_elements: Elements) {
    self._elements = _elements
    self._position = _elements.startIndex
  }
  init(_elements: Elements, _position: Elements.Index) {
    self._elements = _elements
    self._position = _position
  }
}
extension IndexingIterator: IteratorProtocol, Sequence {
  public typealias Element = Elements.Element
  public typealias Iterator = IndexingIterator<Elements>
  public typealias SubSequence = AnySequence<Element>
  
  public mutating func next() -> Elements.Element? {
    if _position == _elements.endIndex { return nil }
    let element = _elements[_position]
    _elements.formIndex(after: &_position)
    return element
  }
}
  • _elements:需要迭代的集合,類(lèi)型為 Collection;
  • _position:記錄遍歷時(shí)的位置信息;

IndexingIterator 的作用主要是在迭代器執(zhí)行 next() 方法時(shí),記錄了 position,通過(guò) position 記錄索引的同時(shí),還可以與 elements.endIndex 比較來(lái)判斷是否有下一個(gè)元素。

Slice

Collection 協(xié)議中,通過(guò)閉包去訪(fǎng)問(wèn)多個(gè)元素時(shí),返回了一個(gè) Slice<Self>,這里的 Slice 是一個(gè)可以獲取集合的元素的子序列。切片存儲(chǔ)的只是集合的開(kāi)始和結(jié)束索引,所以它不會(huì)將集合中的元素復(fù)制一份,因此創(chuàng)建切片具有O(1)復(fù)雜度。Collection 中通過(guò) Slice 來(lái)截取子序列,具體可以看下 Slice 到底做了些什么:

public struct Slice<Base: Collection> {
  public var _startIndex: Base.Index
  public var _endIndex: Base.Index
  internal var _base: Base

  public init(base: Base, bounds: Range<Base.Index>) {
    self._base = base
    self._startIndex = bounds.lowerBound
    self._endIndex = bounds.upperBound
  }
  public var base: Base {
    return _base
  }
}
extension Slice: Collection {
  public typealias Index = Base.Index
  public typealias Indices = Base.Indices
  public typealias Element = Base.Element
  public typealias SubSequence = Slice<Base>
  public typealias Iterator = IndexingIterator<Slice<Base>>

  public var startIndex: Index {
    return _startIndex
  }
  public var endIndex: Index {
    return _endIndex
  }
  public subscript(index: Index) -> Base.Element {
    get {
      _failEarlyRangeCheck(index, bounds: startIndex..<endIndex)
      return _base[index]
    }
  }
  public subscript(bounds: Range<Index>) -> Slice<Base> {
    get {
      _failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
      return Slice(base: _base, bounds: bounds)
    }
  }
}

Slice 實(shí)質(zhì)上僅且修改 Collection 的索引,從而達(dá)到獲取子序列的效果。

Custom Collection

在了解了 Collection 及其相關(guān)的一些協(xié)議 后,我們來(lái)嘗試自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Collection,這個(gè)集合包含 1 ~ 10 的 Int 類(lèi)型的元素,并支持下標(biāo)訪(fǎng)問(wèn)和多次遍歷:

struct SquareCollection: Collection { 
    typealias Element = Int
    typealias Index = Int
    typealias Indices = Range<Int>
    let contents = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    var indices: Range<Int> = 0..<10
    
    public func index(after i: Int) -> Int {
        return i + 1
    }
    public var startIndex: Int {
        get {
            return indices.startIndex
        }
    }
    public var endIndex: Int {
        get {
            return indices.endIndex
        }
    }
    public subscript(index: Int) -> Element {
        get {
            return contents[index]
        }
    }
}

我們?cè)?SquareCollection 實(shí)現(xiàn)了 Collection 協(xié)議,提供了索引功能,然后通過(guò) subscript 方法就可以達(dá)到通過(guò)下標(biāo)訪(fǎng)問(wèn)元素的效果:

let collection = SquareCollection()
print(collection[1])  // 2
print(collection[3])  // 4

這個(gè)自定義的集合,有點(diǎn)類(lèi)似一個(gè)寫(xiě)死的數(shù)組,它擁有 Sequence 與 Collection 協(xié)議的所有已實(shí)現(xiàn)的方法,比如:

let newCollection = collection.dropLast()
print(newCollection.count) // 9

當(dāng)然這離真正的數(shù)組還有很遠(yuǎn)的距離,比如設(shè)置初始化元素、越界判斷、通過(guò)下標(biāo)修改元素值,關(guān)于通過(guò)下標(biāo)修改元素,Swift 中通過(guò) MutableCollection 協(xié)議來(lái)實(shí)現(xiàn),可以看下 MutableCollection 的核心代碼:

public protocol MutableCollection: Collection
where SubSequence: MutableCollection
{
  override subscript(position: Index) -> Element { get set }
  override subscript(bounds: Range<Index>) -> SubSequence { get set }
}

extension MutableCollection {
  public subscript(bounds: Range<Index>) -> Slice<Self> {
    get {
      _failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
      return Slice(base: self, bounds: bounds)
    }
    set {
      _writeBackMutableSlice(&self, bounds: bounds, slice: newValue)
    }
  }
}

最核心的是重寫(xiě)了 subscript 方法,提供了對(duì)外的 set 方法,當(dāng)然集合家族中,還有非常的輔助協(xié)議,用來(lái)提高集合家族的效率與性能,這里暫不深入講解,后面內(nèi)容涉及到的話(huà)再詳細(xì)介紹。

集合與算法

集合大家族中, 除了定義相關(guān)的數(shù)據(jù)結(jié)構(gòu)外,還提供了大量的算法實(shí)現(xiàn),以提高 Swift 的便捷性與高性能,以 Sequence 為例,我們可以看下 SequenceAlgorithms.swift 內(nèi)容:

public func enumerated() -> EnumeratedSequence<Self>
public func min() -> Element?
public func max() -> Element?
public func starts<PossiblePrefix: Sequence>(
    with possiblePrefix: PossiblePrefix
  ) -> Bool
public func elementsEqual<OtherSequence: Sequence>
...
...

SequenceAlgorithms 實(shí)現(xiàn)了大量的基礎(chǔ)算法,其具體的實(shí)現(xiàn)就不展示討論,當(dāng)然 CollectionAlgorithms 也是類(lèi)似的,目的就是讓開(kāi)發(fā)者更便捷地使用集合的同時(shí)又保證它的性能穩(wěn)定。如果我們開(kāi)發(fā)組件或者開(kāi)源庫(kù)的時(shí)候,這是一種值得參考的思路,在功能完善的同時(shí),我們還要保證使用者的便捷與接口的高性能,當(dāng)然這是題外話(huà)了,就不過(guò)多去討論。

Lazy

What is Lazy

Lazy 惰性加載,最典型的用例是懶加載,定義時(shí)聲明具體操作,但只有在第一次使用時(shí)才去真正創(chuàng)建:

// 僅聲明了 titleLabel,并沒(méi) UILabel 對(duì)象
lazy var titleLabel : UILabel = {
    let label = UILabel()
    return label
}() 
// 訪(fǎng)問(wèn) titleLabel 時(shí)會(huì)去創(chuàng)建 UILabel 對(duì)象
self.titleLabel

通過(guò)這么一個(gè)簡(jiǎn)單的例子,初步了解 Swift 的 Lazy 是什么意思,當(dāng)然懶加載只是其中的一種用法,下面我們來(lái)了解下集合中的 Lazy 作用。

LazySequence

在 Sequence 和 Collection 中已經(jīng)了很多類(lèi)似 mapfilter 這些高階函數(shù),它們的思路是將一些通用的算法封裝成簡(jiǎn)單的單行函數(shù)。但是有些時(shí)候我們并不需要立馬執(zhí)行這些輔助數(shù),我們希望是的使用時(shí)再去執(zhí)行輔助函數(shù),定義時(shí)只做聲明,這個(gè)時(shí)候就需要使用到 lazy,舉個(gè)例子:

let allUser = [0, 1, 0, 1, 0, 2, 3, 4, 2, 1]
// 定義時(shí)直接執(zhí)行 filter 方法,如果 agents 沒(méi)有被使用,則浪費(fèi)了資源
let agents = allUser.filter { $0 == 1 }

// 使用時(shí)才執(zhí)行 filter 方法,如果不使用也不會(huì)浪費(fèi)資源
let lazyAgents = allUser.lazy.filter { $0 == 1 }

Swift SequencesCollections 給我提供通過(guò) lazy 的方式來(lái)執(zhí)行輔助函數(shù),目的是為了給開(kāi)發(fā)者提供便捷的輔助函數(shù)并又保持其高性能。那么這里來(lái)看下 Sequences 是怎么實(shí)現(xiàn) lazy 的。

我們可以通過(guò)查看 LazySequence.swift 了解具體實(shí)現(xiàn),核心代碼如下:

public protocol LazySequenceProtocol : Sequence {
  associatedtype Elements: Sequence = Self where Elements.Element == Element
  var elements: Elements { get }
}
extension LazySequenceProtocol where Elements == Self {
  public var elements: Self { return self }
}
extension LazySequenceProtocol {
  public var lazy: LazySequence<Elements> {
    return elements.lazy
  }
}
extension LazySequenceProtocol where Elements: LazySequenceProtocol {
  public var lazy: Elements {
    return elements
  }
}
public struct LazySequence<Base : Sequence> {
  internal var _base: Base
  internal init(_base: Base) {
    self._base = _base
  }
}

從源碼角度來(lái)看,非常有意思的是 LazySequence 并沒(méi)有做任何的額外事情,只是新定義了一種類(lèi)型。那么它具體是怎么實(shí)現(xiàn),這個(gè)我們需要看具體的輔助方法,比如 Filter,我們可以從 Filter.swift 中找到以下代碼:

extension LazyFilterSequence.Iterator: IteratorProtocol, Sequence {
  public typealias Element = Base.Element
  public mutating func next() -> Element? {
    while let n = _base.next() {
      if _predicate(n) {
        return n
      }
    }
    return nil
  }
}

Lazy 的功能,實(shí)質(zhì)上是通過(guò) extension 方法,在迭代器執(zhí)行 next() 方法時(shí),對(duì)獲取到的元素執(zhí)行 filter() 后再進(jìn)行返回,從而實(shí)現(xiàn)了 lazy 惰性執(zhí)行的效果。當(dāng)然其它的 map()、flatMap() 等方法 lazy 實(shí)現(xiàn)也是一樣的,這里就不一一列舉了。

總結(jié)

在這一章內(nèi)容中,介紹 Collection 大家族中幾個(gè)重要的協(xié)議,初步了解它們是如何去實(shí)現(xiàn)與工作的。 但是,它們?cè)?Collection 大家族中,不過(guò)是冰山一角,還有更多的內(nèi)容等待我們?nèi)ド钊胪诰?,我們可以嘗試通過(guò)解決下面幾個(gè)問(wèn)題去加深對(duì)集合類(lèi)型的了解:

  • 有幾種不同形式的 SequenceCollection,它們的作用是什么?
  • 為什么 IteratorLazySequence 等使用的是值類(lèi)型,這樣的目的是為了什么?
  • SequenceCollection 有哪些高階函數(shù),他們是如何去實(shí)現(xiàn)的?

如果能答對(duì)上面的問(wèn)題,估計(jì)會(huì)對(duì) Swift 的集合類(lèi)型有更深入的了解,下一篇我們會(huì)從 Array、Dictionary、Set 的具體實(shí)現(xiàn)來(lái)深入了解 Swift 的集合家族,同時(shí)也會(huì)嘗試去實(shí)現(xiàn)我們自定義的功能齊全的集合類(lèi)型。

更多內(nèi)容,請(qǐng)關(guān)注我的公眾號(hào):

關(guān)注公眾號(hào)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容