谈谈 Swift 中 Sequence(序列) 、Collection(集合) 和高阶函数

2021-11-26 13:08:31 浏览数 (1)

1. 前言


序列和集合是一门语言中重要的组成部分,下面我们就通过这篇文章来看看 Swift 中的序列和集合。

首先我们来看一段简单的代码:

代码语言:javascript复制
let numbers = [1,2,3,4]
for num in numbers {
    print(num)
}

这是一段简单的通过 for...in 遍历数组中元素的代码,那么这个 for...in 在底层是如何实现的呢?下面我们通过 sil 代码来看一看:

这里贴关键的代码:

  • 首先调用了 Collection 协议中的 makeIterator()方法,创建了一个 indexingIterator
  • 接下来调用 IndexingIterator.next()方法来不断拿到元素
  • 所以我们平时使用的 for...in就是个语法糖,底层是通过迭代器来实现遍历的。

下面我们通过 Swift 源码来看看,首先找到 Collection.swift文件:

下面我们就开始研究一下Sequence

2. Sequence(序列)


▐ 2.1 IteratorProtocol

首先我们找到 Sequence.swift文件,首先看到的就是 IteratorProtocol协议:

代码语言:javascript复制
public protocol IteratorProtocol {  associatedtype Element  mutating func next() -> Element?}

以上就是协议定义的源码,一个关联类型 Element和一个 mutatingnext方法,返回一个 Element。

▐ 2.2 Sequence

继续向下看,就可以看到 Sequence的源码:

可以看到 Sequence协议:

  • 可以表达一个有限或者无限的集合
  • 它只提供集合中的元素和如何访问这些元素的接口
▐ 2.3 小结

总的来说:IteartorProtocol是一个一次提供一个序列值的类型,它和Sequence协议时息息相关的,Sequence每次通过创建迭代器来访问序列中的元素。

所以我们每次在使用 for...in的时候,其实都是使用这个集合的迭代器来遍历当前的集合或者序列中的元素。

▐ 2.4 自定义Sequence

下面我们来自定义一个 Sequence,假设我们要用一个结构体来模拟一个集合,对于一个给定的初始值,那么当前集合中包含从 0...count的整形集合。

代码语言:javascript复制
struct LGSequence: Sequence {

    // 指定Element类型为Int
    typealias Element = Int

    var arrayCount: Int

    init(_ count: Int) {
        self.arrayCount = count
    }

    // 为Sequence创建一个迭代器,来遍历Seq中的元素
    func makeIterator() -> LGIterator{
        return LGIterator(self)
    }
}


/// 迭代器,遵循 IteratorProtocol 协议
struct LGIterator: IteratorProtocol {

    // 指定Element类型为Int
    typealias Element = Int

    let seq: LGSequence

    var count = 0

    // 提供一个构造方法,方便初始化迭代器
    init(_ sequence: LGSequence) {
        self.seq = sequence
    }

    // next 方法以count作为自增的操作
    mutating func next() -> Int? {

        guard count < self.seq.arrayCount else {
            return nil
        }

        count  = 1
        return count
    }
}

let seq = LGSequence.init(10)

for element in seq {
    print(element)
}

打印结果:

此时我们就提供了一个简单的 Sequence,虽然是结构体实现的,但是在使用过程中与几何特性非常相似。

所以 SequenceIterator两者之间的关系就如下图所示:

遵守 Sequence协议后同样可以使用协议中默认实现的 mapfilterreduce等高阶函数。这个我们后面会继续说。

3. Collection(集合)


在 Swift 的数组和字典中也广泛运用了

Collection协议,在一开始我们看sil代码的时候也看到了Collection调用makeIterator()方法的身影。

Collection.swift源码中我们看完IndexingIterator源码后就可以看到Collection的源码,挺多的。

截取一段:

可以看到 Collection协议遵守了Sequence协议,里面代码还是很多的,感兴趣的下载源码去看看,这里我们通过一个案例来加深对Collection协议的理解。

▐ 3.1 Collection协议案例

这里我们设计一个环形缓冲区,首先来了解一下环形缓冲区:

可以理解为一个环形的数据结构,往里面插入数据,取出数据,如下图所示。

  • 刚初始化的时候头尾节点均指向同一节点
  • 当插入一个节点后,头指针不变,尾指针指向它的下一个节点
  • 当满了的时候尾指针指向nil
  • 遍历读取的时候,头指针 1

下面我们实现一个可以插入元素和读取元素的结构体:

代码语言:javascript复制
struct RingBuffer<Element> {
    // 这里我们使用 ContiguousArray 来存储数据
    // ContiguousArray 也就是Array的底层实现
    var _buffer: ContiguousArray<Element?>

    var headIndex: Int = 0
    var tailIndex: Int = 0

    init(capacity: Int) {
        _buffer = ContiguousArray<Element?>(repeating: nil, count: capacity)
    }

    mutating func append(_ value: Element) {
        _buffer[tailIndex % _buffer.count] = value
        tailIndex  = 1
    }


    mutating func read() -> Element? {
        let element = _buffer[headIndex % _buffer.count]
        headIndex  = 1

        return element
    }
}


var b = RingBuffer<Int>(capacity: 10)

for i in 1...10 {
    b.append(i)
}

for _ in 1...10 {
    let element = b.read()
    print(element!)
}

<!--打印结果-->
1
2
3
4
5
6
7
8
9
10

以上就是我们实现的一个简单的环形缓冲区。下面我们来优化一下:

  1. 首先我们知道模运算的开销是很大的,是否可以替换掉
  2. 我们目前需要频繁的移动 tailIndex
  3. 如果满了我们该怎么判断?是不是 headIndex = tailIndex
  4. 该如何删除一个元素

下面我们就一个一个的解决上面提到的问题:

首先是模运算的替换,看过 Objective-C 或者 Swift 底层源码的同学都知道,苹果在底层经常会用到一个叫做mask的值,也就是掩码。在objc_msgSend查找缓存的时候,计算index通过sel & mask= index。下面我们就要知道这个mask的值是如何取得的。

通常情况下mask的取值范围是2^n - 1

所以有这么一个表达式:x % y = x & (y - 1),其中y的取值是2^n,一个数对2^n取模相当于一个数和2^n - 1做按位与运算。

举个例子:

3 % 4 = 3 & 3 5 % 4 = 5 & 3

所以如果我们的环形缓冲区的大小是2^n的话,我们是不是就可以直接通过与运算的方式来计算index。那么我们就要在初始化的时候,通过计算得到一个稍大的与想要的大小最接近的2^n的容量。其实苹果源码中有这样一段代码:

代码语言:javascript复制
extension FixedWidthInteger {
    /// Returns the next power of two.
    @inlinable
    func nextPowerOf2() -> Self {
        guard self != 0 else {
            return 1
        }
        // 1 左移 当前 bitWidth - (sefl - 1)的前导0 的位数
        return 1 << (Self.bitWidth - (self - 1).leadingZeroBitCount)
    }
}

所以修改后的代码如下:

代码语言:javascript复制
struct RingBuffer<Element> {
    // 这里我们使用 ContiguousArray 来存储数据
    // ContiguousArray 也就是Array的底层实现
    var _buffer: ContiguousArray<Element?>

    var headIndex: Int = 0
    var tailIndex: Int = 0

    // 提供一个mask 计算属性,获取掩码
    var mask: Int {
        return _buffer.count - 1
    }


    init(capacity: Int) {
        // 获取一个与当前大小最接近的2^n的值,作为容量
        // 比如是10,这里获取到的就是16
        let c = capacity.nextPowerOf2()
        _buffer = ContiguousArray<Element?>(repeating: nil, count: c)
    }

    // 移动尾
    mutating func advanceTailIndex(by: Int) {
        tailIndex = indexAdvanced(index: tailIndex, by: by)
    }

    // 移动头
    mutating func advanceHeadIndex(by: Int) {
        headIndex = indexAdvanced(index: headIndex, by: by)
    }

    // 抽取 的 方法
    func indexAdvanced(index: Int, by: Int) -> Int {
        return (index   by) & mask
    }

    mutating func append(_ value: Element) {
        _buffer[tailIndex] = value
        advanceTailIndex(by: 1)
    }


    mutating func read() -> Element? {
        let element = _buffer[headIndex]
        advanceHeadIndex(by: 1)

        return element
    }
}


var b = RingBuffer<Int>(capacity: 10)

for i in 1...10 {
    b.append(i)
}

for _ in 1...10 {
    let element = b.read()
    print(element!)
}

<!--打印结果与上面相同-->

至此我们就解决了前两个问题,下面我们就解决缓存区满的问题:

代码语言:javascript复制
mutating func append(_ value: Element) {

    if _buffer[tailIndex] != nil {
        fatalError("out of bounds")
    }

    _buffer[tailIndex] = value
    advanceTailIndex(by: 1)

    if tailIndex == headIndex {
        print("the buffer is full")
    }
}
  • 如果缓冲区满了,这里面我们采用报错的方式
  • 按照数组的设计应该是在快要满的时候进行扩容
  • 这里判断满的条件我们是假定headIndex没有移动过的,即使移动过也是遍历了整个缓冲区的。

说了这么多还没见Collection的身影,下面我们就来实现一下Collection协议,并通过遵循Collection协议来实现删除的方法

Collection协议官方文档

在官方文档中我们可以看到我们必须要实现的属性和方法:

  • 定义startIndexendIndex属性,表示集合起始和结束的位置;
  • 定义一个只读的下标操作符;
  • 实现一个index(after:)方法用于在集合中移动索引位置;

所以我们简单实现Collection协议后的代码如下:

代码语言:javascript复制
extension RingBuffer: Collection {

    var startIndex: Int {
        return headIndex
    }

    var endIndex: Int {
        return tailIndex
    }

    subscript(position: Int) -> Element {
        get {
            return self._buffer[position]!
        }

        set {
            self._buffer[position] = newValue
        }
    }

    func index(after i: Int) -> Int {
        return (i   1) & mask
    }
}

测试一下:

代码语言:javascript复制
var b = RingBuffer<Int>(capacity: 3)

for i in 1...3 {
    b.append(i)
}

print(b.startIndex)
print(b.endIndex)
b[3] = 6
print(b[3])
print(b.isEmpty)
print(b.first!)

<!--打印结果-->
0
3
6
false
1

当遵循了Collection协议后就可以使用协议中默认实现的属性和方法,蛮多的,感兴趣的可以跳转过去看看,或者去源码里面找一找。

下面我们来实现删除元素:

如果我们想要Remove掉一个元素可以使用Swift标准库中的协议RangeReplaceableCollection

这个协议允许我们通过一个集合来替换当前集合中任意自己的元素,同时支持我们删除和插入元素的操作。

当我们自定义集合要遵循RangeReplaceableCollection的时候,我们需要提供一个默认的init方法,以及replaceSubrange(_ with:)方法。其他的如果不需要都可以使用默认实现。源码自己看一下吧,这里就不放了,也挺多的。

下面我们就来实现这两个方法,和想要的remove方法:

RangeReplaceableCollection源码中我们还可以看到,除了默认的init方法,还允许我们用一个Sequence集合来初始化,这里我们可以改进一下,提供一个字面量的初始化方法:

代码语言:javascript复制
extension RingBuffer: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: Element...) {
        self.init(elements)
    }
}

var buffer: RingBuffer = [1,2,3,4]

下面我们实现一些删除的逻辑:

代码语言:javascript复制
extension RingBuffer: RangeReplaceableCollection {
    init() {
        self.init(16)
    }

    mutating func remove(at position: Int) -> Element {

        // 判断一下是否越界(也不是很完善,不越界也有可能取到空值)
        if position > _buffer.count - 1 || position < 0 {
            fatalError("Index out of range")
        }
        var currentIndex = position

        // 取出要删除位置的值
        // 其实我们这里会存在空值的情况,就是没有完全使用ContiguousArray的空间
        // 为了方便计算位置,开辟的空间为2^n
        // 所以存在取出空值的情况
        guard let element = self._buffer[position] else {
            fatalError("Index out of range")
        }

        // 到这里就不会取出空值了
        switch position {
        case self.headIndex:
            // 删除的是头结点
            self._buffer[currentIndex] = nil

            var nextIndex = self.indexAdvanced(index: position, by: 1)
            // 头结点在最后一个元素的位置
            if nextIndex == tailIndex {
                self.advanceTailIndex(by: -1)
                // 将头结点置为初始位置
                headIndex = 0
                // 删没了
                if _buffer[0] == nil { tailIndex = 0 }
            } else {
                // 头结点不是最后一个节点,移动后面的元素
                // 如果下一个位置不是尾,则将尾部元素前移
                while nextIndex != self.tailIndex {
                    // swapAt 交换两个位置的元素
                    self._buffer.swapAt(currentIndex, nextIndex)
                    // 移动位置8
                    currentIndex = nextIndex
                    nextIndex = self.indexAdvanced(index: currentIndex, by: 1)
                }

                self.advanceTailIndex(by: -1)
            }

        default:
            // 置空要删除的元素
            self._buffer[position] = nil
            // 获取删除位置的下一个位置
            var nextIndex = self.indexAdvanced(index: position, by: 1)
            // 如果下一个位置不是尾,则将尾部元素前移
            while nextIndex != self.tailIndex {
                // swapAt 交换两个位置的元素
                self._buffer.swapAt(currentIndex, nextIndex)
                // 移动位置8
                currentIndex = nextIndex
                nextIndex = self.indexAdvanced(index: currentIndex, by: 1)
            }

            self.advanceTailIndex(by: -1)
        }

        return element
    }
}

以上我默认元素的存储都是从数组的0位开始的。

对于删除的是head为的元素做单独处理,详见注释。

4. 高阶函数


什么是高阶函数呢?高阶函数也是函数,有两个特点

  • 接受函数或者闭包作为参数
  • 返回值是一个函数或者闭包

这些函数我们常常用来作用于ArraySetDictionary中的每一个元素。

▐ 4.1 Map 函数

Map函数作用于Collection中的每一个元素,然后返回一个新的Collection

举个例子,我们又一个大写的String类型的Array,数组中存储着文件的后缀名,我们在对比后缀的时候通常采用小写的去对比,文件的后缀名不一定都是小写的,所以需要将数组中的所有后缀名都转换为小写的。

代码语言:javascript复制
let suffArray = ["Png", "Gif", "DOC", "PDF"]

var newArray = [String]()

for element in suffArray {
    newArray.append(element.lowercased())
}

print(newArray)

如果有了map函数处理起来就很简单了:

代码语言:javascript复制
let suffArray = ["Png", "Gif", "DOC", "PDF"]
let newSuffs = suffArray.map {$0.lowercased()}
print(newSuffs)

下面我们来看看map函数的实现,map函数在Sequence协议和Collection协议中均有扩展的,这里我们在源码中测试发现是调用的Collection协议中的map函数来到Collection.swift文件中

代码语言:javascript复制
extension Collection {
  /// Returns an array containing the results of mapping the given closure
  /// over the sequence's elements.
  ///
  /// In this example, `map` is used first to convert the names in the array
  /// to lowercase strings and then to count their characters.
  ///
  ///     let cast = ["Vivien", "Marlon", "Kim", "Karl"]
  ///     let lowercaseNames = cast.map { $0.lowercased() }
  ///     // 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
  ///     let letterCounts = cast.map { $0.count }
  ///     // 'letterCounts' == [6, 6, 3, 4]
  ///
  /// - Parameter transform: A mapping closure. `transform` accepts an
  ///   element of this sequence as its parameter and returns a transformed
  ///   value of the same or of a different type.
  /// - Returns: An array containing the transformed elements of this
  ///   sequence.
  @inlinable
  public func map<T>(
    _ transform: (Element) throws -> T
  ) rethrows -> [T] {
    // TODO: swift-3-indexing-model - review the following
    let n = self.count
    if n == 0 {
      return []
    }

    var result = ContiguousArray<T>()
    result.reserveCapacity(n)

    var i = self.startIndex

    for _ in 0..<n {
      result.append(try transform(self[i]))
      formIndex(after: &i)
    }

    _expectEnd(of: self, is: i)
    return Array(result)
  }
}

我们可以看到这是一个泛型函数,接受一个闭包作为参数,并返回新的集合。

在函数内部我们可以看到:

  • 首先创建一个同样带下的ContiguousArray数组
  • 让遍历原数组中的元素,对每个元素执行闭包中的操作
  • 最后将执行完闭包的元素放到新创建的集合中返回

在研究flatMap的时候知道可选值也有一个对应的flatMap,所以map也有,源码如下,在Optional.swift文件中:

代码语言:javascript复制
  @inlinable
  public func map<U>(
    _ transform: (Wrapped) throws -> U
  ) rethrows -> U? {
    switch self {
    case .some(let y):
      return .some(try transform(y))
    case .none:
      return .none
    }
  }
▐ 4.2 flatMap 函数

flat翻译过来是平的意思,那么其作用也是在map的基础上使返回的集合扁平化,还有一个作用就是过滤空值(这个功能已经废弃了,如果需要过滤空值可以使用CompactMap),下面我们还是通过一个例子来更加直观的感受一下flatMap的作用。

代码语言:javascript复制
let nums = [[1, 2, 3], [4, 5, 6]]
let result = nums.flatMap{$0}
print(result)

<!--打印结果-->
[1, 2, 3, 4, 5, 6]

通过打印结果我们可以看到,通过flatMap函数后,最终返回了一个一维数组。

如果使用map函数则打印结果跟原数组是一样的。下面我们就来看看flatMap函数的源码,看看它在底层是如何实现的。

SequenceAlgorithms.swift文件中我们可以找到flatMap的源码实现:

代码语言:javascript复制
extension Sequence {
  /// Returns an array containing the concatenated results of calling the
  /// given transformation with each element of this sequence.
  ///
  /// Use this method to receive a single-level collection when your
  /// transformation produces a sequence or collection for each element.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `flatMap` with a transformation that returns an array.
  ///
  ///     let numbers = [1, 2, 3, 4]
  ///
  ///     let mapped = numbers.map { Array(repeating: $0, count: $0) }
  ///     // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
  ///
  ///     let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
  ///     // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
  ///
  /// In fact, `s.flatMap(transform)`  is equivalent to
  /// `Array(s.map(transform).joined())`.
  ///
  /// - Parameter transform: A closure that accepts an element of this
  ///   sequence as its argument and returns a sequence or collection.
  /// - Returns: The resulting flattened array.
  ///
  /// - Complexity: O(*m*   *n*), where *n* is the length of this sequence
  ///   and *m* is the length of the result.
  @inlinable
  public func flatMap<SegmentOfResult: Sequence>(
    _ transform: (Element) throws -> SegmentOfResult
  ) rethrows -> [SegmentOfResult.Element] {
    var result: [SegmentOfResult.Element] = []
    for element in self {
      result.append(contentsOf: try transform(element))
    }
    return result
  }
}
  • 我们可以看到flatMapSequence协议的扩展
  • 同样也是个泛型函数,泛型是遵守Sequence协议的
  • 接收一个闭包参数
  • 返回一个遵循Sequence协议的集合
  • 在函数体内部首先初始化一个空的集合
  • 然后遍历self,对集合中的元素尝试执行闭包函数
  • 然后拼接在开始创建的集合中

看完函数的实现我们也就理解了为什么一个二维数组调用flatMap函数后会返回一个一维数组。其实如果是一个三维数组执行完flatMap函数后会返回一个二维数组,依次类推,所谓扁平化就是这个意思吧,降低数组的维度。

当然还有其他flatMap函数,它的源码在Optional.swiftResult.swift中:

代码语言:javascript复制
// Optional.swift
@inlinable
public func flatMap<U>(
_ transform: (Wrapped) throws -> U?
) rethrows -> U? {
switch self {
case .some(let y):
  return try transform(y)
case .none:
  return .none
}
}

对于可选值的flatMap,则是判断是否为空,空值就返回空,否则对其中的元素执行闭包函数。

代码语言:javascript复制
// Result.swift
  public func flatMap<NewSuccess>(
    _ transform: (Success) -> Result<NewSuccess, Failure>
  ) -> Result<NewSuccess, Failure> {
    switch self {
    case let .success(success):
      return transform(success)
    case let .failure(failure):
      return .failure(failure)
    }
  }

    public func flatMapError<NewFailure>(
    _ transform: (Failure) -> Result<Success, NewFailure>
  ) -> Result<Success, NewFailure> {
    switch self {
    case let .success(success):
      return .success(success)
    case let .failure(failure):
      return transform(failure)
    }
  }

对于Result中的flatMap分为两种,分别是对成功时执行闭包函数,还有就是flatMapError是对失败时执行闭包函数。

关于可选值的flatMapmap的区别就是map是将执行完闭包的结构包装在some中,就是还是个可选值,flatMap是直接返回执行完闭包的结果。所以对于可选值使用map函数会返回一个可选的可选值,使用flatMap返回一个可选值。

▐ 4.3 CompactMap 函数

简单的说CompactMap函数就是过滤掉空值的,我们来看个例子:

代码语言:javascript复制
let nums = [[1, 2, 3], [4, 5, 6], nil]
let result = nums.compactMap{$0}
print(result)

<!--打印结果-->
[[1, 2, 3], [4, 5, 6]]

可以看到nil被过滤掉了,下面我们看看CompactMap的源码,在SequenceAlgorithms.swift文件中,同样是Sequence协议的扩展。

代码语言:javascript复制
extension Sequence {
  /// Returns an array containing the non-`nil` results of calling the given
  /// transformation with each element of this sequence.
  ///
  /// Use this method to receive an array of non-optional values when your
  /// transformation produces an optional value.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `compactMap` with a transformation that returns an optional `Int` value.
  ///
  ///     let possibleNumbers = ["1", "2", "three", "///4///", "5"]
  ///
  ///     let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
  ///     // [1, 2, nil, nil, 5]
  ///
  ///     let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
  ///     // [1, 2, 5]
  ///
  /// - Parameter transform: A closure that accepts an element of this
  ///   sequence as its argument and returns an optional value.
  /// - Returns: An array of the non-`nil` results of calling `transform`
  ///   with each element of the sequence.
  ///
  /// - Complexity: O(*m*   *n*), where *n* is the length of this sequence
  ///   and *m* is the length of the result.
  @inlinable // protocol-only
  public func compactMap<ElementOfResult>(
    _ transform: (Element) throws -> ElementOfResult?
  ) rethrows -> [ElementOfResult] {
    return try _compactMap(transform)
  }

  // The implementation of flatMap accepting a closure with an optional result.
  // Factored out into a separate functions in order to be used in multiple
  // overloads.
  @inlinable // protocol-only
  @inline(__always)
  public func _compactMap<ElementOfResult>(
    _ transform: (Element) throws -> ElementOfResult?
  ) rethrows -> [ElementOfResult] {
    var result: [ElementOfResult] = []
    for element in self {
      if let newElement = try transform(element) {
        result.append(newElement)
      }
    }
    return result
  }
}

看完源码也就很清晰了,关于泛型函数和闭包参数就不多说了,为什么能过滤空值,其实就是一个简单的if let,简单到令人发指。

▐ 4.4 filter函数
filterSequence协议扩展中提供的方法,允许调用者传入一个闭包来过滤掉集合中的元素:
代码语言:javascript复制
let arr = [1, 2, 3, 4, 6]

let result = arr.filter{ $0 % 2 == 0 }
print(result)

<!--打印结果-->
[2, 4, 6]

以上代码就是过滤掉数组中的奇数。下面我们就来看看它的源码:

代码语言:javascript复制
 @inlinable
  public __consuming func filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {
    return try _filter(isIncluded)
  }

  @_transparent
  public func _filter(
    _ isIncluded: (Element) throws -> Bool
  ) rethrows -> [Element] {

    var result = ContiguousArray<Element>()

    var iterator = self.makeIterator()

    while let element = iterator.next() {
      if try isIncluded(element) {
        result.append(element)
      }
    }

    return Array(result)
  }

源码也很简单,就是通过对集合中的每个元素执行闭包的返回结果就像判断如果是true就添加到创建的结合中,最后返回。

▐ 4.5 forEach

对于集合类型的元素,有时候不必要都通过for循环来去做遍历,Sequence同样提供了高阶函数来供我们使用

代码语言:javascript复制
let arr = [1, 2, 3, 4, 6]
arr.forEach{print($0)}

看一下它的底层实现:

代码语言:javascript复制
@_semantics("sequence.forEach")
@inlinable
public func forEach(
_ body: (Element) throws -> Void
) rethrows {
    for element in self {
      try body(element)
    }
}

也很简单,就是内部通过for...in进行一次遍历,然后执行闭包函数。

那么这个时候我们想记录一下当前元素的index该如何去做呢?需要自己定义变量去做累加吗?并不需要,此时Swift的函数式编程就显得很有魅力:

代码语言:javascript复制
let arr = [1, 2, 3, 4, 6]

arr.enumerated().forEach {
    print("index:($0) --- value:($1)")
}

<!--打印结果-->
index:0 --- value:1
index:1 --- value:2
index:2 --- value:3
index:3 --- value:4
index:4 --- value:6

enumerated函数的实现如下,在SequenceAlgorithms.swift文件中:

代码语言:javascript复制
@inlinable public func enumerated() -> EnumeratedSequence<Array<Element>>

@inlinable // protocol-only
  public func enumerated() -> EnumeratedSequence<Self> {
    return EnumeratedSequence(_base: self)
}

下面我们研究一下EnumeratedSequence,在Algorithm.swift文件中:

代码语言:javascript复制
@frozen
public struct EnumeratedSequence<Base: Sequence> {
  @usableFromInline
  internal var _base: Base

  /// Construct from a `Base` sequence.
  @inlinable
  internal init(_base: Base) {
    self._base = _base
  }
}

这里可以看到EnumeratedSequence是一个接收Sequence协议的结构体。

此时的forEach函数调用的是下面的

代码语言:javascript复制
@inlinable public func forEach(_ body: (Iterator.Element) throws -> Void) rethrows

我们找到Iterator

▐ 4.6 Reduce 函数

我们可以使用reduce函数合并集合中所有元素创建一个新值。

代码语言:javascript复制
let numbers = [1,2,3]
let result = numbers.reduce(10,  )
print(result)

<!--打印结果-->
16

下面我们看看reduce函数的源码,在SequenceAlgorithms.swift文件中,是Sequence协议的扩展:

代码语言:javascript复制
@inlinable
  public func reduce<Result>(
    _ initialResult: Result,
    _ nextPartialResult:
      (_ partialResult: Result, Element) throws -> Result
  ) rethrows -> Result {
    var accumulator = initialResult
    for element in self {
      accumulator = try nextPartialResult(accumulator, element)
    }
    return accumulator
}

我们可以看到reduce函数接收两个参数,一个是泛型的的初始值,和一个闭包函数。

函数体中:

  • 定义一个accumulator变量,记录初始值
  • for循环遍历集合中的元素,对每一个元素执行闭包和accumulator进行处理
  • 最后将执行完闭包的值存储到accumulator
  • 最后返回accumulator

5. LazySequence


lazy就是懒加载的意思,懒加载序列就是在用到的时候在加载,下面我们来看个例子:

代码语言:javascript复制
let numbers = Array(1...10000)
let mapNumbers = numbers.map{$0 * 2}

这个数组是很大的,执行完map后就会把一个新的数组全部返回给mapNumbers,但是我们并不一定全部用到里面的数据。所以使用懒加载的方式就很合适了:

代码语言:javascript复制
let numbers = Array(1...10000)
let mapNumbers = numbers.lazy.map{$0 * 2}

print(mapNumbers[0])

此时我们po mapNumbers的数据发现并没有任何改变,只是在取值的时候通过闭包的执行返回一条数据,即使取值后mapNumbers的数据也是不改变的,对于lazy返回的值是只读的,即使是使用var修饰。

使用lazy关键字后返回的是一个LazySequence的值,下面我们就去源码中看看LazySequence

LazySequence.swift文件中我们可以看到:

lazySequence扩展中的一个计算属性,返回了一个LazySequence对象。

LazySequenceinit方法中就很简单了,直接保留了原有的集合数据。

LazySequence遵守了Sequence协议,提供了生成迭代器的方法。

LazySequenceProtocol协议的扩展中实现了map协议,其实对于上面的高阶函数都有实现,这里我们就不一一介绍了。这里返回了一个LazyMapSequence

LazyMapSequence的源码中,我们可以看到初始化的方法中保存了当前的集合和闭包函数。

LazyMapSequence的扩展中,访问元素的时候在执行闭包函数,这也就是我们获取元素的时候才会得到相应的结果,返回的集合中,存储的是原有的集合数据。是真正的懒加载。

所以lazy实质上是保存当前集合和对应的操作,然后在访问元素的时候,执行对应的操作。

0 人点赞