让自定义类型支持 for-in:三分钟实现 Sequence
需求
自己写了一个"分页加载器",想这样用:
swift
for page in Paginator(pageSize: 20) {
print("拿到 \(page.items.count) 条数据")
}
实现
swift
struct Page {
let items: [String]
}
struct PageIterator: IteratorProtocol {
let pageSize: Int
private var current = 1
init(pageSize: Int, current: Int = 1) {
self.pageSize = pageSize
self.current = current
}
mutating func next() -> Page? {
// 模拟 3 页就结束
guard current <= 3 else { return nil }
defer { current += 1 }
return Page(items: (0..<pageSize).map { "第\($0)条" })
}
}
struct Paginator: Sequence {
let pageSize: Int
// 只要实现 makeIterator() 即可
func makeIterator() -> PageIterator {
PageIterator(pageSize: pageSize)
}
}
// 验证
for (idx, page) in Paginator(pageSize: 5).enumerated() {
print("第 \(idx + 1) 页:\(page.items)")
}
要点
- 只要
makeIterator()
返回的对象能满足IteratorProtocol
,就能享受所有Sequence
的"语法糖":for-in
、map
、filter
、stride
、enumerated()
... - 想支持"倒序"?再写个
ReversedCollection
即可,零侵入。
Switch 模式匹配进阶:写个"小型解析器"
JSON 节点建模
swift
indirect enum JSON {
case null, bool(Bool), number(Double), string(String)
case array([JSON])
case object([String: JSON])
}
一行代码计算"叶子节点数"
swift
func leafCount(_ node: JSON) -> Int {
switch node {
case .null, .bool, .number, .string: // 原子节点
return 1
case .array(let arr):
return arr.map(leafCount).reduce(0, +)
case .object(let dict):
return dict.values.map(leafCount).reduce(0, +)
}
}
利用"值绑定 + where"做校验
swift
func validate(_ json: JSON) -> Bool {
switch json {
case .object(let dict) where dict["version"] != nil:
return true
case .array(let arr) where !arr.isEmpty:
return true
default:
return false
}
}
结论:
- switch 不仅能"匹配值",还能"解构 + 过滤",天然适合 AST、路由表、状态机。
- 配合
indirect enum
可无限嵌套,写编译器前端都够用。
控制流 × 结构化并发
异步循环:逐页下载直到空数据
swift
struct RemoteLoader: AsyncSequence {
typealias Element = [String]
let pageSize: Int
struct AsyncIterator: AsyncIteratorProtocol {
let pageSize: Int
private var page = 1
init(pageSize: Int, page: Int = 1) {
self.pageSize = pageSize
self.page = page
}
mutating func next() async throws -> Element? {
// 模拟网络请求
try await Task.sleep(nanoseconds: 300_000_000)
guard page <= 3 else { return nil } // 第 4 页开始空数据
defer { page += 1 }
return (0..<pageSize).map { "Item-\(page)-\($0)" }
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator(pageSize: pageSize)
}
}
// 使用
Task {
for try await batch in RemoteLoader(pageSize: 5) {
print("下载到 \(batch.count) 条 \(batch)")
}
print("全部下载完成")
}
要点
AsyncSequence
与Sequence
语义完全一致,只是"迭代"变成异步。- 用
for try await
就能像同步世界一样写"循环",避免了回调地狱。 - 早期退出:
break
可直接离开异步循环;Task.checkCancellation()
配合try Task.sleep()
实现可取消的下载器。
defer 实战:数据库事务模板方法
问题
事务套路重复:
- begin
- try 块里做 SQL
- 成功 commit / 失败 rollback
- 还要处理连接关闭
封装
swift
enum DBError: Error { case rollback, commit }
func inTransaction<T>(_ work: (Connection) throws -> T) rethrows -> T {
let conn = try dbPool.acquire()
try conn.execute("BEGIN")
var needRollback = true
// 无论正常 return 还是抛错,都保证最后释放连接
defer {
dbPool.release(conn)
}
// 第二个 defer:只要 work 不崩溃,就一定 rollback(除非被显式覆盖)
defer {
if needRollback {
try? conn.execute("ROLLBACK")
}
}
let result = try work(conn)
try conn.execute("COMMIT")
// 如果走到这里,说明 commit 成功;把 rollback defer 取消掉
func cancelRollback() {
needRollback = false
} // 空函数,仅用于语法占位
cancelRollback() // 调用后,前一个 defer 不再执行(⚠️ 技巧:Swift 没有"撤销 defer"语法,这里通过"提前覆盖"实现)
return result
}
使用方零心智负担:
swift
let newId = try inTransaction { conn in
try conn.run("INSERT INTO user(name) VALUES (?)", "Alice")
return conn.lastInsertRowID
}
// 离开作用域:commit 已做,连接已还池。
综合案例:用控制流写个"命令行扫雷"骨架
展示如何把"循环 + switch + where + defer"全部串起来。
swift
enum Cell: String {
case mine = "💣", empty = "⬜️", flag = "🚩"
}
struct Board {
let size: Int
private(set) var cells: [[Cell]]
init(size: Int) {
self.size = size
cells = Array(repeating: Array(repeating: .empty, count: size), count: size)
}
subscript(x: Int, y: Int) -> Cell {
get { cells[y][x] }
set { cells[y][x] = newValue }
}
func isValid(x: Int, y: Int) -> Bool {
x >= 0 && y >= 0 && y < size && x < size
}
mutating func randomSetMines() {
for x in 0..<size {
for y in 0..<size {
if Bool.random() {
self[x, y] = Cell.mine
}
}
}
}
func printSelf(visible: Bool) {
cells.forEach { cs in
for c in cs {
if visible {
print(c.rawValue, terminator: " ")
} else {
if case .mine = c {
print(Cell.empty.rawValue, terminator: " ")
} else {
print(c.rawValue, terminator: " ")
}
}
}
print()
}
print()
}
}
func parseInput(_ s: String) -> [Int]? {
let sChars = s.split(separator: " ")
if sChars.count < 2 {
return nil
}
return sChars.map { ss in
Int(ss) ?? 0
}
}
// 游戏主循环
func game() {
var board = Board(size: 9)
var firstStep = true
board.randomSetMines()
board.printSelf(visible: true)
print("游戏开始啦")
gameLoop: while true {
board.printSelf(visible: false)
// 读坐标
print("输入 x y [f](f 代表插旗):", terminator: "")
guard let line = readLine(),
let ints = parseInput(line), ints.count >= 2,
board.isValid(x: ints[0], y: ints[1]) else {
print("格式或越界,重试")
continue gameLoop
}
let (x, y, isFlag) = (ints[0], ints[1], ints.count == 3)
switch (board[x, y], isFlag) {
case (.mine, false) where !firstStep: // 第一次不炸
print("Game Over")
break gameLoop
case (.empty, true):
board[x, y] = .flag
case (.flag, true): // 再按一次取消
board[x, y] = .empty
default:
break
}
firstStep = false
}
// defer 保证打印结束语
defer { print("欢迎再来!") }
}
// 运行
game()
控制流技巧复盘:
gameLoop
标签精准跳出多重嵌套。switch
用 tuple + where 一次性判断"状态 + 动作"。defer
做"扫尾",即使未来加return
也不会漏掉结束语。
避坑指南:最容易踩的 5 个暗礁
坑 | 现象 | 官方一句话 | 正确姿势 |
---|---|---|---|
switch 空 case | 编译报错 | case 必须至少一条语句 | 用 break 占位或合并 |
fallthrough 误解 | 还想绑定值 | fallthrough 不能带值绑定 | 把共用逻辑抽函数 |
defer 顺序 | 后注册的先执行 | 栈结构 | 把"先开后关"写在一起 |
AsyncSequence 取消 | 死循环不退 | 不会自动检查 | 循环内显式 try Task.checkCancellation() |
guard let 作用域 | 外部取不到 | guard 绑定才延续 | 用 guard let x = x else { return } |
结语:把"控制流"变成"业务流"
- 任何复杂业务,都能拆成"循环 + 分支 + 提前退出"三种原语。
- 先写"快乐路径",再用
guard
把异常路径平行展开,代码会突然清爽。 switch
的模式匹配,本质是"把 if-else 链压缩成语义表",让机器帮你查表。defer
是"把释放逻辑提到申请点旁边",降低心智距离,比 Java 的finally
更细粒度。- 结构化并发让"异步循环"与"同步循环"共享同一套关键词,未来是
AsyncSequence
的天下。