Swift标准库源码之旅 - Sequence.swift

背景

sequence作为我阅读Swift源码的第一篇原因是集合类型是一个编程语言中可以说是使用非常广泛而且它们的方法非常多(尤其对于Swfit),使用过程当中也会好奇各个函数的时间复杂度,这些方法怎么选择。二是因为Sequence这个协议太厉害了,有23个协议或者结构体遵循了它. 可以说是Swift集合类型的核心基础. 我们平常用到的map/forEach方法就是Sequence的扩展函数.

笔者通过挨个分析标准库中Swift文件,并对照写一个MyXXX.swift的方式学习理解Swift语言. 水平有限,主要作为记录,如果有幸其他同学感兴趣可以一起交流研究

Sequence Type

中文名为序列,与数组的集合类型不同的是,sequence可以是无限的,并且没有count属性,虽然它可以用来for循环迭代看似里面有很多元素的样子,但其实sequence严格来讲并不能说是一个拥有某种元素的容器类型.

如果是容器类型,对于一个容器它里面装的东西是固定的,我们遍历它就是一件一件往出掏里面的元素,掏完了还得放回去,再次遍历时候还是掏元素。但是序列不一样,遍历行为取决于它的定义,可能它的遍历是一直掏一个值10次,而且遍历完之后不一定放回去。

为什么这么说,从它的定义可以看出来

协议定义

Swift作为声称面向协议编程的语言,基础框架和抽象都是通过协议来组织的.

protocol IteratorProtocol {
    associatedtype Element
    mutating func next() -> Element?
}

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

要迭代的类型Element当然是个泛型,所以使用associatedType延迟确定,

Iterator可以理解为一个遍历服务员,用来定义遍历行为的,它要迭代的数据类型当然要与sequence一样。
可以看出来Sequence这个类型唯一要求的就是要实现next()方法来返回一个数值,至于next方法里你是从容器里取的还是一直返回同一个元素都是可以的,并没有做要求。

无限序列

所以我们可以自己写一个这样的无限序列

struct UnlimitedSequence: Sequence {
    typealias Element = Int
    
    struct Iterator: IteratorProtocol {
        typealias Element = Int
        func next() -> Int? {
            return 1
        }
    }
    
    func makeIterator() -> UnlimitedSequence.Iterator {
        return Iterator()
    }
}

哦对了,要写一个遍历方法。


extension Sequence {
    func forEach(handler: (Iterator.Element) -> Void) {
        var iterator = makeIterator()
        while let value = iterator.next() {
            handler(value)
        }
    }
}

遍历方法非常简单,创建一个迭代器对象,一直取它的next,直到取的值nil结束为止

如果我们对上面的无限序列进行遍历就会陷入无尽的循环中。不过无限序列的应用场景还是有的,这里不展开讨论了。

不稳定序列

多次迭代结果会不同的序列

上面说到的掏出来不一定放回去是什么意思呢,现象就是第一次遍历这个序列是一种结果,第二次遍历的结果与第一种又不同了。这可能会让你觉得不可控,但是这也是序列的一个特点,我们可以很容易的写出来这样一个序列

class IntSequence: Sequence {
    typealias Element = Int
    typealias Iterator = IntIterator
    
    var maxNumber: Int
    init(maxNumber: Int) {
        self.maxNumber = maxNumber
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.maxNumber = 10
        }
    }
    func makeIterator() -> IntIterator {
        return IntIterator(number: maxNumber)
    }
}

struct IntIterator: IteratorProtocol {
    let number: Int
    var current = 0
    init(number: Int) {
        self.number = number
    }
    mutating func next() -> Int? {
        defer {
            current += 1
        }
        return current > number ? nil : current
    }
}

这个序列第一次遍历时候根据传入的int值从0开始遍历到这个number,第二次遍历时同样创建一个遍历器,但是这个遍历器的number值已经被我们修改掉了..所以遍历的值自然也会更改.

这个例子非常的粗暴,仅仅是为了让你看出来如果我们自己创建一个sequence类型是可以让它产生这种行为的。因为sequence本身没有要求我们达到多次遍历稳定的目的。其实这个特点并不能算做是副作用,我们确实有遍历会产生消耗的实际场景,例如网络流,磁盘上的文件. 调用完Next使其销毁有时候反而是我们的目的。

扩展函数

作为可迭代类型的根协议,sequence做的非常的克制,Swift的面向协议思想也是如此,一个协议只定义单一的事就好。只要你可以进行迭代,就可以考虑用sequence进行建模. 并且你将收获到一堆好用的函数(比如我前面定义的IntSequence)。我们看看一些常用的函数

func map<T>(_ transform: (Iterator.Element) -> T) -> [T] {
        var iterator = makeIterator()
        var result: [T] = []
        while let value = iterator.next() {
            result.append(transform(value))
        }
        return result
}
    
func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
        var iterator = makeIterator()
        var result: [Element] = []
        while let value = iterator.next() {
            if isIncluded(value) {
                result.append(value)
            }
        }
        return result
}
    
func first(_ predicate: (Element) -> Bool) -> Element? {
        var iterator = makeIterator()
        while let value = iterator.next() {
            if predicate(value) {
                return value
            }
        }
        return nil
}

原来这些都是定义到了sequence中 这样可以最大程度的复用这些函数,比如除了数组我们常用到的Range也可以使用这些方法也是因为它也是一个Sequence

DropFirst ...Sequence?

对于dropFirst函数我们使用数组的时候偶尔会用到,我起初也以为它跟map一样是sequence的一个扩展函数,跳过前k个元素返回一个数组,然而它的实现是这样的

extension Sequence {
    func dropFirst(_ k: Int = 1) -> DropFirstSequence<Self> {
        return DropFirstSequence(self, dropping: k)
    }
}

What's the DropFirstSequence

struct DropFirstSequence<Base: Sequence> {
    private let base: Base
    private let limit: Int
    
    init(_ base: Base, dropping limit: Int) {
        self.base = base
        self.limit = limit
    }
}

extension DropFirstSequence: Sequence {
    typealias Element = Base.Element
    typealias Iterator = Base.Iterator
    
    func makeIterator() -> Iterator {
        var it = base.makeIterator()
        var dropped = 0
        while dropped < limit, it.next() != nil {
            dropped &+= 1
        }
        return it
    }
    
    func dropFirst(_ k: Int) -> DropFirstSequence<Base> {
        return DropFirstSequence(base, dropping: limit + k)
    }
}

简单来说,这是一个新的结构体,它遵循了sequence,它持有调用dropFirst之前的原序列,然后在遍历的时候也是用的原序列的Iterator,只不过是跳过前k个元素的.

下面这段看起来是个废话-

笔者想了一下,这个与直接返回一个去掉了前k个元素的数组相比,首先它的返回值不是数组,是另外一个结构体。重要的是,这个函数时间复杂度是O(1),它并没有执行任何的遍历操作。当使用这个新的结构体序列进行遍历的时候才会遍历,但是如果返回drop之后的结果数组必然需要进行遍历操作生成数组,然后使用新数组遍历时候又要进行一遍遍历操作。注释里有一句 lazily consumes 应该就是指这个意思。
但是如果我们drop之后没有进行遍历还需要当数组使用的话这个优化看起来是没用了。适用于drop之后接着进行迭代的情况。

优化遍历的序列

像上面的DropFirstSequence用来优化一些操作后的遍历性能的还有两个.

PrefixSequence DropWhileSequence

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容