为什么用重音符号分割字符串会崩溃?

Why does splitting a string with accents crash?

这应该很简单……

import Foundation

let str:String = "Beyonce\u{301} and Tay"
print(str)

print(str.components(separatedBy: CharacterSet(charactersIn: "e")))

编译正常,直到我 运行 可执行文件:

// Beyoncé and Tay
// Illegal instruction (core dumped)

我怀疑 Swift 很难处理组合 '\u{65}' 重音标记,但考虑到该语言对基于字素的字符串模型的强调程度,我认为很明显在 'e' 上拆分 "Beyonce\u{301} and Tay" 应该只给出 ["B", "yonce\u{301} and Tay"],因为 'e\u{301}' 应该被解释为单个字素而不是 'e' 加上组合锐音符。

单个字符上拆分不会崩溃:

print(str.components(separatedBy: "e"))
// ["B", "yoncé and Tay"]

我的swift版本是

swiftc -version
Swift version 3.0-dev (LLVM 3e3d712024, Clang 09ad59b006, Swift fdf6ee20e4)
Target: x86_64-unknown-linux-gnu

Swift 的 Linux 端口似乎有错误。我不会在我的回答中解决这个问题。下面的代码在 Mac OS X.

上进行了测试

您 运行 遇到了 Unicode 规范化问题。字母 é 可以用两种方式表示,Swift 认为相同:

let s1 = "e\u{301}" // letter e + combining acute accent
let s2 = "\u{0e9}"  // small letter e with acute

s1.characters.count // 1
s2.characters.count // 1
s1 == s2            // true

那是因为 Swift 的 String 和它的前身 NSString 一样对 Unicode 有很好的支持。但是如果你深入研究,你会开始看到一些不同之处:

s1.utf16.count // 2
s2.utf16.count // 1

因此,即使 s1s2 相等,它们的存储方式也不同:使用 2 或 1 个代码点。 components(seperatedBy: ) 对这个事实视而不见。它遍历字符串中的所有代码点并在找到字母 e 时拆分。在一种形式到另一种形式之间的转换称为 规范化 并影响函数的工作方式:

let str1 = "Beyonce\u{301} and Tay"
let str2 = str1.precomposedStringWithCanonicalMapping // normalize the string to Form C

let charset = CharacterSet(charactersIn: "e")
str1.components(separatedBy: charset) // ["B", "yonc", "́ and Tay"]
str2.components(separatedBy: charset) // ["B", "yoncé and Tay"]

参考文献: