Swift 控制流深度解析(二):模式匹配、并发与真实项目套路

让自定义类型支持 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-inmapfilterstrideenumerated() ...
  • 想支持"倒序"?再写个 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("全部下载完成")
}

要点

  • AsyncSequenceSequence 语义完全一致,只是"迭代"变成异步。
  • for try await 就能像同步世界一样写"循环",避免了回调地狱。
  • 早期退出:break 可直接离开异步循环;Task.checkCancellation() 配合 try Task.sleep() 实现可取消的下载器。

defer 实战:数据库事务模板方法

问题

事务套路重复:

  1. begin
  2. try 块里做 SQL
  3. 成功 commit / 失败 rollback
  4. 还要处理连接关闭

封装

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 }

结语:把"控制流"变成"业务流"

  1. 任何复杂业务,都能拆成"循环 + 分支 + 提前退出"三种原语。
  2. 先写"快乐路径",再用 guard 把异常路径平行展开,代码会突然清爽。
  3. switch 的模式匹配,本质是"把 if-else 链压缩成语义表",让机器帮你查表。
  4. defer 是"把释放逻辑提到申请点旁边",降低心智距离,比 Java 的 finally 更细粒度。
  5. 结构化并发让"异步循环"与"同步循环"共享同一套关键词,未来是 AsyncSequence 的天下。
相关推荐
1024小神15 小时前
xcode 配置了AppIcon 但是不显示icon图标
ios·swiftui·swift
奶糖 肥晨16 小时前
架构深度解析|基于亚马逊云科技与Swift Alliance Cloud构建高可用金融报文交换架构
科技·架构·swift
Swift社区2 天前
用 Task Local Values 构建 Swift 里的依赖容器:一种更轻量的依赖注入思路
开发语言·ios·swift
TouchWorld2 天前
iOS逆向-哔哩哔哩增加3倍速播放(4)- 竖屏视频·全屏播放场景
ios·swift
1024小神2 天前
swift中使用ObservableObject单利模式的时候,用let 或 @ObservedObject 或 @StateObject 有什么区别
开发语言·ios·swift
1024小神2 天前
swift中 列表、字典、集合、元祖 常用的方法
数据结构·算法·swift
TouchWorld3 天前
iOS逆向-哔哩哔哩增加3倍速播放(3)-[横屏视频-全屏播放]场景
ios·swift
东坡肘子3 天前
周日小插曲 -- 肘子的 Swift 周报 #115
人工智能·swiftui·swift
gf13211114 天前
python_字幕文本、音频、视频一键组合
python·音视频·swift