Swift 中 String
忘了 String 是字符数组
在 Swift 里,String 已经彻底不再是一个集合类型。而是一个提供了从多个维度展现一个 Unicode 视图的类型。你可以得到它的多个 Characters,可以看到它的 UTF-8 / UTF-16 / Unicode scalar 值等等。
String 和 NSString 处理 Unicode 时的差异
unicode 长度是可变的,我们将看到 unicode 另外一个可变的特性,即组成同一个字符的 code unit 组合也是可变的。而区分 String 和 NSString 的一个重要方式,就是它们对 unicode 的这个特性的处理方式,是不同的。为了理解这个事情,我们从 unicode grapheme clusters 说起。
Unicode grapheme clusters
首先,我们定义一个字符串:
let cafe = "Caf\u{00e9}" |
返回
Café
Swift 里,我们可以使用 \u {} 这样的方式使用 unicode scalar 定义 unicode 字符。
对于单词 Café 中的最后一个字符来说,它的 unicode scalar 是 U+00E9,名字是 LATIN SMALL LETTER E WITH ACUTE。每一个 unicode 都有一个 scalar 值以及一个全大写字母表示的名称。
为了表示这个字符 é,除了使用它的 unicode scalar 外,我们可以用两个其它的 unicode 字符拼起来:
- 英文字母 e,它的 unicode scalar 是 U0065,name 是 LATIN SMALL LETTER E;
- 声调字符’,它的 unicode scalar 是 U0301,name 是 COMBINING ACUTE ACCENT;
当我们把这两个字符像下面这样组合起来的时候:
let cafee = "Caf\u{0065}\u{0301}" |
返回
Café
尽管 cafee 的定义中貌似有 5 个字符,但实际显示出来的最后一个字符和之前用 unicode scalar 定义是一样的。我们管 \u {0065}\u {0301} 就叫做 grapheme cluster。
既然同一个 unicode 字符可以有多种表现形式,那么由不同 code unit 构成的字符串相等么?对此,Swift 中的 String 和 Objective-C 中的 NSString 处理方式却是有差别的,这种差别,是区分它们最明显的地方之一。
Canonically equivalent
为了能识别上面 cafe 和 cafee 的情况,unicode 规范中提出了一个概念:canonically equivalent。如何理解它呢?我们先来看 Swift 是如何识别 cafe 和 cafee 的。
Swift String
当我们要读取一个字符串中所有的字符时,可以访问 String 对象的 characters 属性,在下个视频中,我们会更多讲到它的用法:
let cafe = "Caf\u{00e9}" |
返回
4
4
尽管 cafee 中最后一个字符的定义使用了两个 code unit,Swift 可以识别的 Character 中字符的个数也是 4。
但是,当我们查看 cafe 和 cafee 的 UTF-8 和 UTF-16 编码的个数时,就能看到它们的区别了:
cafe.utf8.count |
返回
5
6
4
5
为什么会这样呢?我们用 UTF-8 编码举例:
对于 cafe 来说,é 的 UTF-8 编码是 C3 A9,加上前面 Caf 的编码是 43 61 66,因此 cafe 的 UTF-8 编码个数是 5;
对于 cafee 来说,声调字符’的 UTF-8 编码是 CC 81,加上前面 Cafe 的 UTF-8 编码是 43 61 66 65,因此是 6 个,它相当于 Cafe’;
理解了之后,你可以自己去推算一下 UTF-16 的情况。
尽管 cafe 和 cafee 的编码方式不同,当我们在 Swift 中,比较 cafe 和 cafee 时,结果会是 true:
cafe == cafee |
返回
true
这就是 unicode canonically equivalent 的含义,通过这些例子,你也能更好的了解到 Swift 在 unicode 表意正确上作出的努力。
NSString
而当我们把这些例子用在 NSString 上,情况就会有些不同。用同样的 code unit 定义下面两个 NSString 对象:
let nsCafe = NSString(characters: [0x43, 0x61, 0x66, 0xe9], length: 4) |
返回
Café
4
Café
5
可以看到,同样是使用不同的 code unit 构建字符串”Café”,在 NSString 看来,它们是长度不同的两个字符串。
因此,当我们比较 nsCafe 和 nsCafee 的时候,结果也是没有意外的 false:
nsCafe == nsCafee |
返回
false
因此,== 对 NSString 来说,并没有执行 canonically equivalent 的语义。为了在不同的 NSString 对象之间进行语义比较,我们只能这样:
let result = nsCafe.compare(nsCafee as String) |
返回
NSComparisonResult
true
可以看到,这样就能按照 canonically equivalent 的方式判断相等了。