Swift Regex:进阶用法
Swift Regex: Beyond the basics
2022年6月6日
一句话判断
Swift 5.7 的 Regex 系统远不止是正则表达式的语法糖——它是一套类型安全的模式匹配框架,支持 DSL 构建、强类型捕获、Foundation 解析器集成,以及自定义消费组件。
这场 Session 讲了什么
Swift 5.7 引入了全新的 Regex 类型,但这个 Session 的重点不是基础正则语法,而是那些让 Swift Regex 独树一帜的高级特性。
RegexBuilder DSL。 你可以用声明式的 DSL 语法来构建正则表达式,而不是写一串难以阅读的字符串。每个匹配步骤都是类型安全的,编译器会帮你检查捕获组的类型。
强类型捕获(Strongly-typed captures)。 传统的正则表达式捕获组返回的都是字符串,你需要手动转换类型。Swift Regex 的 TryCapture 和 Capture 可以直接指定捕获的输出类型,如果转换失败,整个匹配自动失败。
Foundation 解析器集成。 Float.parseFloatStrategy、Int.parseIntStrategy、.date(format:) 等 Foundation 提供的解析策略可以直接嵌入 Regex,让你在匹配的同时完成数据转换。
CustomConsumingRegexComponent。 你可以创建自己的正则组件,只要实现 consuming 方法即可。这意味着你可以把任何自定义的解析逻辑封装成 Regex 组件,和标准组件无缝组合。
重复行为的精细控制。 默认的正则量词是贪婪的(eager),但 Swift Regex 提供了 .eager、.reluctant(又名 lazy)和 .possessive 三种重复行为,让你精确控制匹配策略。
值得深挖的点
类型安全的捕获改变了正则的使用方式。 在其他语言中,正则匹配返回的是 Match 对象,你通过索引取捕获组,然后手动做类型转换。Swift Regex 的 Regex<(Substring, Int, Double)> 这种泛型设计让捕获组的类型在编译期就确定了。这意味着你永远不会因为下标越界或类型转换失败而在运行时崩溃——这类错误在编译期就被消灭了。
RegexBuilder DSL 的可读性是一种权衡。 DSL 语法虽然比原始正则字符串更易读,但它也更冗长。Session 的建议是:简单模式用字符串字面量,复杂模式(特别是需要强类型捕获的)用 DSL。两种方式可以自由混搭,用 Regex 初始化一个子表达式,然后在 DSL 中引用它。
代码片段
import RegexBuilder
// 强类型捕获 + Foundation 解析器
let priceRegex = Regex {
"$"
TryCapture {
OneOrMore(.digit)
Optionally {
"."
Repeat(.digit, count: 2)
}
} transform: { raw in
Float.parseFloat(raw)
}
One(.whitespace)
Capture {
OneOrMore(.word)
} transform: { raw in
raw.lowercased()
}
}
// 匹配 "$12.99 Coffee" -> (Substring, Float, Substring)
if let match = "$12.99 Coffee".firstMatch(of: priceRegex) {
let (_, price, item) = match.output
print("Item: \(item), Price: \(price)") // 类型安全!
}
// 自定义正则组件
struct IPv4Address: CustomConsumingRegexComponent {
typealias Output = Substring
func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) -> (upperBound: String.Index, output: Substring)? {
let octet = Repeat(.digit, count: 1...3)
let regex = Regex {
octet; "."; octet; "."; octet; "."; octet
}
return input[index...].firstMatch(of: regex).map { match in
(match.range.upperBound, match.output)
}
}
}
// 重复行为控制
let reluctantMatch = "aaa".firstMatch(of: Regex {
OneOrMore(.any, .reluctant) // 尽可能少匹配
"a"
})
// 匹配 "aa" 而不是 "aaa"
最佳实践
- 简单的模式用字符串字面量
#"..."#,复杂的或需要类型安全的用 RegexBuilder DSL。 - 优先使用
TryCapture而不是Capture+ 手动转换,让编译器帮你处理错误路径。 - 利用 Foundation 的解析策略(
parseIntStrategy、parseFloatStrategy、date(format:))避免重复造轮子。 - 对于性能敏感的场景(比如解析大文件),用
.possessive重复行为来避免不必要的回溯。
还有什么值得关注
Regex类型遵循Codable,你可以把正则表达式序列化存储。- Swift Regex 的底层使用的是基于 NFA 的高效匹配引擎,性能和手写的解析器相当。
CharacterClass提供了大量预定义的字符集(.digit,.word,.whitespace,.hexDigit等),避免你自己写字符范围。- 正则表达式的字面量语法和 DSL 语法可以在同一个表达式中混用。