Swift 5.1正则表达式拆分异常,请求排查原因
问题分析与解决方案:Swift正则拆分字符串时的偏移量错误
这不是Swift正则表达式的Bug,问题出在你的代码里混用了两种完全不同的字符串位置度量体系——Swift原生的扩展字形集群索引和Foundation框架里的UTF-16代码单元位置,导致计算出来的偏移量完全不匹配。
问题根源拆解
- Swift的
String是基于**扩展字形集群(Extended Grapheme Clusters)**设计的:简单说就是你视觉上看到的每个“字符”就算一个单位,count属性返回的就是这个单位的数量。比如你字符串里的“اً”是由两个Unicode标量组合成的一个视觉字符,在Swift里算1个单位,但在UTF-16编码里它占2个代码单元。 - 而
NSRegularExpression返回的匹配范围NSRange,是基于UTF-16代码单元的位置来计算的,和Swift的String.Index不是一一对应的。
你的代码里犯了一个关键错误:直接把NSRange的location(UTF-16位置)当作Swift字符串的Int偏移量来使用,比如self[start ..< end]里的end是匹配到的\n的UTF-16位置(10),但这个位置对应的Swift字符串索引已经跳过了\n本身,导致你截取的前半部分包含了\n,后半部分又跳过了Very High的第一个字符V,最终得到错误的输出。
修复后的代码
要解决这个问题,你需要放弃用Int跟踪位置的方式,改用Swift原生的String.Index来处理,并且把NSRange正确转换为Swift的Range<String.Index>。修改后的split(regex:)方法如下:
func split(regex pattern: String) throws -> [String] { let regex = try NSRegularExpression(pattern: pattern, options: []) let fullRange = NSRange(startIndex..., in: self) let matches = regex.matches(in: self, options: [], range: fullRange) var parts = [String]() var currentStart = startIndex for match in matches { guard let matchRange = Range(match.range, in: self) else { continue } // 添加匹配项之前的子串 parts.append(String(self[currentStart..<matchRange.start])) // 更新起始位置到匹配项的末尾 currentStart = matchRange.end } // 添加最后剩余的子串 if currentStart < endIndex { parts.append(String(self[currentStart...])) } return parts }
现在再运行你的测试代码:
print(try! "مرتفع جداً\nVery High".split(regex: "\n"))
就能得到预期的输出:["مرتفع جداً", "Very High"]
内容的提问来源于stack exchange,提问作者Ala'a Al Hallaq
相关产品推荐
相关产品推荐

