Meet Swift Regex
Swift & UI 进阶 20m

认识 Swift Regex

Meet Swift Regex

2022年6月6日

在 Apple 官方观看视频

一句话判断

Swift Regex 把正则表达式从一个”写出问题”的工具变成了一个”结构化、类型安全、可组合”的字符串处理引擎——这可能是今年 Swift 语言层面最重要的更新。

这场 Session 讲了什么

Swift 在语言层面引入了正则表达式支持,提供三种创建方式:

Regex 字面量:用 /.../ 包裹正则语法,编译器提供语法高亮和编译时错误检查。语法兼容 Perl、Python、Ruby、Java、NSRegularExpression。

运行时字符串构造:从字符串动态创建 Regex,适用于搜索框等需要用户输入模式的场景。输出类型为 AnyRegexOutput,因为捕获组的类型在编译时未知。

Regex Builder:声明式的结构化正则构建器,类似 SwiftUI 的 ViewBuilder。可以在 Builder 中嵌入字面量,平衡简洁性和可读性。

Session 用一个金融交易日志解析的场景贯穿全程,演示了如何用 Regex Builder 解析包含交易类型、日期、机构和金额的非结构化文本。

值得深挖的点

Foundation 解析器的无缝集成是 Swift Regex 最具突破性的设计。在 Regex Builder 中,你可以直接使用 .date(format:...).currency(...) 等 Foundation 提供的解析器,而不是手写日期和金额的正则。这意味着日期格式的复杂性(月/日/年还是日/月/年)交给 Foundation 处理,你的正则只描述结构。

强类型捕获组解决了传统正则的”字符串 everywhere”问题。当你用字面量或 Builder 定义捕获组时,编译器知道每个组的类型。日期捕获组直接返回 Date 类型,金额捕获组返回 Decimal 类型。不再是全是 String 的数组需要手动转换。

NegativeLookahead 的精确控制。在匹配”任意字符直到分隔符”这种常见模式时,简单的 .+ 会导致大量回溯。NegativeLookahead 允许你告诉正则引擎”看到什么就停下来”,大幅提升匹配效率。

代码片段

用 Regex Builder 解析交易记录:

import RegexBuilder

// 定义字段分隔符:2个以上空格或制表符
let fieldSeparator = Regex {
    OneOrMore(.whitespace)
} | "\t"

// 完整的交易行解析器
let transactionRegex = Regex {
    // 交易类型:CREDIT 或 DEBIT
    Capture { "CREDIT" | "DEBIT" }
    fieldSeparator
    
    // 日期:使用 Foundation 的日期解析器
    TryCapture {
        .date(format: "{month}/{day}/{year}", locale: Locale(identifier: "en_US"))
    }
    fieldSeparator
    
    // 机构名称:任意字符直到遇到分隔符
    Capture {
        OneOrMore {
            NegativeLookahead { fieldSeparator }
            CharacterClass.any
        }
    }
    fieldSeparator
    
    // 金额:使用 Foundation 的货币解析器
    TryCapture {
        .currency(code: "USD", locale: Locale(identifier: "en_US"))
    }
}

// 使用:获得强类型结果
if let match = line.wholeMatch(of: transactionRegex) {
    let (kind, date, institution, amount) = match.output
    // kind: Substring, date: Date, institution: Substring, amount: Decimal
}

基础字面量用法:

// 匹配一个或多个数字
let digits = /one or more digits/

// 在字符串中查找所有匹配
for match in "abc123def456".matches(of: /(\d+)/) {
    print(match.1) // 输出: "123", "456"
}

替换操作:

// 替换所有字段分隔符为单个制表符
let normalized = input.replacing(fieldSeparator, with: "\t")

最佳实践

  • 优先使用 Foundation 解析器:日期、数字、URL 等标准格式不要手写正则,交给 Foundation
  • 显式指定 Locale:不要隐式依赖系统 locale,在代码中明确声明你的假设
  • 用 NegativeLookahead 控制回溯:避免 .+ 导致的不必要回溯
  • Builder 和字面量混用:结构用 Builder 组织,细节模式用字面量,找到可读性和简洁性的平衡
  • 运行时正则用 try? 处理:用户输入的正则可能语法有误,做好错误处理

还有什么值得关注

  • Swift Regex 的 Unicode 支持是”一等公民”,不像其他语言需要额外处理
  • 强类型捕获组在编译时就能发现类型不匹配的问题
  • Regex Builder 的库扩展性意味着第三方库也可以提供自定义的解析组件
  • Session 建议搭配 “Swift Regex: Beyond the basics” 一起看,深入了解执行控制和性能优化
WWDC 2022