Swift 迭代三巨头(上集):Sequence、Collection 与 Iterator 深度狂飙

🏁 引子:赛道惊魂!迭代引擎的致命故障

赛道上的引擎轰鸣震耳欲聋,天才 Swift 工程师艾拉紧盯着赛车数据面板,额角的冷汗浸透了队服 ------ 连续三次,实时处理赛车传感器数据的系统在迭代时突然宕机,就像一辆顶级 F1 赛车在蒙扎赛道的直道上突然爆胎。

资深架构师杰西拍了拍她的肩,递过闪烁代码的电脑:"问题不在硬件,而在 Swift 迭代的核心协议 ------Sequence 和 Collection 的契约规则,我们被它们表面的简单给骗了。"

在本堂F1调教课中,您将学到如下内容:

  • 🏁 引子:赛道惊魂!迭代引擎的致命故障
  • 📌 揭秘 Sequence:迭代界的 "起步引擎"
  • 核心协议架构
  • 迭代器为何偏爱 struct?
  • Sequence 的两大关键特性
  • 实战案例:stride 的 "赛道表演"
  • 🏎️ Collection 登场:给迭代装上 "稳定尾翼"
  • Collection 的核心承诺
  • 核心协议定义
  • 注意事项:Set 与 Dictionary 的 "特殊规则"
  • 🔧 for...in 的真相:赛道下的机械核心
  • 语法糖脱糖:暴露底层逻辑
  • 自定义 Sequence 实战:倒计时器
  • 致命陷阱:迭代时修改集合(赛道上改零件!)
  • 安全方案:让迭代与修改 "分道扬镳"
  • 🚦 上集收尾:异步赛道的终极挑战

这对搭档即将掀起一场针对 Swift 迭代底层的 "狂飙对决",而他们的对手,是潜伏在代码深处、专门制造崩溃的 "迭代异常兽"。


📌 揭秘 Sequence:迭代界的 "起步引擎"

Sequence 是 Swift 迭代体系的 "最小作战单位",它的契约如同 F1 赛车的起步规则 ------"只要有人需要迭代器,它就会交出一个能持续输出元素直到耗尽的家伙"。

这个规则看似简单,却暗藏玄机,是所有迭代操作的基石。

核心协议架构

要成为 Sequence 的 "合格选手",必须遵守两大硬性要求:

  1. 定义两个关联类型:Element (迭代的元素类型)和Iterator(迭代器类型)。
  2. 实现makeIterator()方法,每次调用都返回一个全新的迭代器。
swift 复制代码
public protocol Sequence {
    associatedtype Element // 迭代的元素类型
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element // 关联的迭代器类型

    func makeIterator() -> Iterator // 生成新迭代器的核心方法
}

而迭代器本身必须遵循IteratorProtocol ,暴露一个带mutating修饰的next()方法 ------ 这是迭代的 "动力输出轴":

swift 复制代码
public protocol IteratorProtocol {
    associatedtype Element
    mutating func next() -> Element? // 每次调用返回下一个元素,耗尽则返回nil
}

迭代器为何偏爱 struct?

你会发现绝大多数迭代器都用 struct 实现,这绝非偶然,而是 Swift 的 "精妙设计":

  • next()mutating方法,值类型(struct)的迭代器能直接更新自身状态(比如当前位置),无需额外同步操作。
  • 复制迭代器时会得到一个 "独立副本",就像给赛车复制了一套完全相同的操控系统,两个迭代器各自推进、互不干扰,彻底避免了共享可变状态的 "赛道事故"。

虽然类也能实现 IteratorProtocol,但值语义天生契合迭代器的 "单次推进、独立可控" 契约,就像 F1 赛车的专属定制部件,远比通用部件更靠谱。

Sequence 的两大关键特性

  1. 单遍迭代特性:Sequence 只承诺 "能迭代一次",就像 F1 赛道的单圈比赛,跑完就结束。有些迭代器是 "一次性消耗品",用完后永远返回 nil,比如懒加载 I/O 流、生成器式 API。
  2. 每次生成新迭代器makeIterator()每次调用都该返回全新实例。就像每次赛车出发前都要重置状态,确保每轮迭代都是 "干净起步",避免多轮循环互相干扰。

实战案例:stride 的 "赛道表演"

stride(from:to:by:)是 Sequence 的典型代表,它无需分配数组,就能像赛车按固定间距前进一样,生成算术序列:

swift 复制代码
// 从0度到360度,每30度取一个值,像赛车按固定路线过弯
for angle in stride(from: 0, through: 360, by: 30) {
    print(angle) // 输出:0、30、60...360
}

这个 Sequence 不会在内存中存储所有角度值,而是每次调用next()时动态生成 ------ 就像赛车实时响应赛道指令,而非提前规划所有路线。这正是 Sequence 的核心魅力:按需生成,高效灵活。


🏎️ Collection 登场:给迭代装上 "稳定尾翼"

Sequence 虽然灵活,但 "单遍迭代" 的特性在很多场景下就像赛车没有尾翼 ------ 缺乏稳定性。

这时,Collection 闪亮登场,它在 Sequence 的基础上增加了三大 "硬核保障",让迭代像 F1 赛车在赛道上疾驰一样稳如泰山。

Collection 的核心承诺

  1. 支持多轮迭代:无论迭代多少次,结果都一致(只要集合本身不被修改)。
  2. 稳定的顺序:元素的迭代顺序固定(除非集合文档明确说明顺序可变)。
  3. 索引与计数:支持索引访问、下标操作和 count 属性,能随时掌握 "赛道进度"。

Swift 中的 Array、Dictionary、Set 都是 Collection 的忠实拥护者,它们凭借这些特性成为日常开发的 "主力赛车"。

核心协议定义

swift 复制代码
public protocol Collection: Sequence {
    associatedtype Index: Comparable // 可比较的索引类型

    var startIndex: Index { get } // 起始索引(赛道起点)
    var endIndex: Index { get } // 结束索引(赛道终点)

    func index(after i: Index) -> Index // 获取下一个索引(下一个弯道)
    subscript(position: Index) -> Element { get } // 下标访问元素(精准定位赛道位置)
}

这些要求解锁了大量优化:map可以提前分配恰好的存储空间,count无需遍历整个集合就能获取 ------ 就像赛车的空气动力学设计,让每一次操作都更高效。

注意事项:Set 与 Dictionary 的 "特殊规则"

虽然 Set 和 Dictionary 都遵循 Collection,但它们的迭代顺序可能在修改后变化。这就像赛车在不同赛道条件下的路线调整,协议本身不承诺顺序稳定,如果你需要固定顺序,一定要选择明确标注 "顺序不变" 的集合类型(比如 Array)。


🔧 for...in 的真相:赛道下的机械核心

我们天天用for item in list,就像赛车手天天踩油门,却很少有人知道底层的 "传动系统" 是如何工作的。其实,这个看似简单的语法糖,背后藏着 Swift 编译器的 "精妙操作"。

语法糖脱糖:暴露底层逻辑

for item in container的本质的是以下代码的简化版,这就是迭代的 "核心机械结构":

swift 复制代码
// 1. 生成集合的迭代器(给赛车装上变速箱)
var iterator = container.makeIterator()
// 2. 循环调用next(),直到返回nil(变速箱持续换挡,直到终点)
while let element = iterator.next() {
    print(element)
}

自定义 Sequence 实战:倒计时器

为了让你更直观理解,我们来打造一个 "倒计时 Sequence",就像赛车起跑前的倒计时:

swift 复制代码
struct Countdown: Sequence {
    let start: Int // 倒计时起始值

    // 生成迭代器,每次调用都返回新实例
    func makeIterator() -> Iterator {
        Iterator(current: start)
    }

    // 嵌套迭代器结构体,遵循IteratorProtocol
    struct Iterator: IteratorProtocol {
        var current: Int // 当前倒计时值

        mutating func next() -> Int? {
            // 小于0则结束倒计时,返回nil
            guard current >= 0 else { return nil }
            // 先返回当前值,再自减(关键:延迟递减,确保起始值能被返回)
            defer { current -= 1 }
            return current
        }
    }
}

// 测试:从3开始倒计时
for number in Countdown(start: 3) {
    print(number) // 输出:3、2、1、0
}

运行这段代码时,编译器会自动将for...in转化为前面的while循环。由于迭代器是 struct(值类型),如果中途复制迭代器,两个副本会各自独立推进 ------ 就像两辆完全相同的赛车,从同一位置出发,却能各自完成自己的赛程。

致命陷阱:迭代时修改集合(赛道上改零件!)

最容易触发 "迭代崩溃" 的操作,就是在循环中修改集合的底层存储。这就像赛车在高速行驶时突然更换轮胎,必然会失控翻车。

看这个典型的 "死亡代码":

swift 复制代码
struct TodoItem {
    var title: String
    var isCompleted: Bool // 是否完成
}

var todoItems = [
    TodoItem(title: "发布技术博客", isCompleted: true),
    TodoItem(title: "录制播客", isCompleted: false),
    TodoItem(title: "审核PR", isCompleted: true),
]

// 错误示范:迭代时删除元素
for item in todoItems {
    if item.isCompleted,
       // 每次都扫描整个数组找索引,效率极低
       let index = todoItems.firstIndex(where: { $0.title == item.title }) {
        todoItems.remove(at: index) // ⚠️ 致命错误:迭代时集合被修改!
    }
}

这段代码会直接崩溃,原因很简单:数组迭代器假设底层存储 "稳定不变",删除元素后,数组的内存布局发生偏移,迭代器就像失去方向的赛车,再也找不到下一个元素的位置。更糟的是,firstIndex每次都会扫描整个数组,让时间复杂度飙升到 O (n²),堪称 "性能灾难"。

安全方案:让迭代与修改 "分道扬镳"

解决这个问题的核心,就是让迭代和修改互不干扰,就像赛车比赛和维修工作分开进行:

  1. 使用 removeAll (where:):让集合自己管理迭代,高效安全:
swift 复制代码
todoItems.removeAll(where: \\.isCompleted) // 一次遍历完成删除,O(n)效率
  1. 创建过滤副本:保留原集合,生成新的过滤后集合:
swift 复制代码
let openTodos = todoItems.filter { !\$0.isCompleted } // 原集合不变,安全无风险

这两种方案都遵循了 "迭代不修改,修改不迭代" 的黄金法则,彻底避开了 "迭代异常兽" 设下的陷阱。


🚦 上集收尾:异步赛道的终极挑战

艾拉和杰西终于破解了同步迭代的核心密码,修复了赛车数据处理系统的崩溃问题。但他们还没来得及庆祝,新的警报又响了 ------ 实时赛车传感器数据是异步流式传输的,传统的同步迭代根本无法应对。

Swift 并发体系中的AsyncSequence,就像迭代界的 "涡轮增压引擎",能处理异步生成的元素,但它也藏着更棘手的 "延迟陷阱" 和 "取消难题"。

下一集,艾拉和杰西将深入异步迭代的 "死亡赛道",解锁for await的终极用法,直面 "异步延迟魔" 的挑战。而 "迭代异常兽" 的真正阴谋 ------ 利用内存泄漏摧毁整个赛车数据系统,也将浮出水面。他们能否凭借对 AsyncSequence 和自定义 Collection 的深刻理解,再次化险为夷?

相关推荐
linweidong5 小时前
网易ios面试题及参考答案(下)
objective-c·swift·ios开发·切面编程·ios面试·苹果开发·mac开发
大熊猫侯佩12 小时前
Swift 迭代三巨头(下集):Sequence、Collection 与 Iterator 深度狂飙
swift·编程语言·apple
大熊猫侯佩12 小时前
Swift 迭代三巨头(中集):Sequence、Collection 与 Iterator 深度狂飙
swift·编程语言·apple
图形学爱好者_Wu12 小时前
每日一个C++知识点|多线程基础
c++·编程语言
1024小神13 小时前
xcode多环境 Dev 、Debug 和 Release变量配置以及怎么切换不同环境
开发语言·macos·ios·swiftui·xcode·swift
1024小神1 天前
Swift中跨view视图组件实现全局状态共享的方式汇总
ios·swiftui·swift
Wcowin1 天前
【自荐】OneClip—— 一款简单专业的 macOS 剪贴板管理工具
mac·swift·粘贴板
iOS阿玮1 天前
苹果开发者后台叕挂了,P0级别的报错!
uni-app·app·apple
Sheffi662 天前
Swift 与 OC 混编底层交互原理
ios·objective-c·swift