为什么像👩;👩;👧;👦;这样的表情符号在SWIFT字符串中会被如此奇怪地对待? [英] Why are emoji characters like 👩👩👧👦 treated so strangely in Swift strings?
问题描述
字符👩👩👧👦(有两个女人、一个女孩和一个男孩的家庭)编码如下:
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F467
GIRL
,
U+200D
ZWJ
,
U+1F466
BOY
所以它的编码非常有趣;它是单元测试的完美目标。然而,斯威夫特似乎不知道如何对待它。我的意思是:
"👩👩👧👦".contains("👩👩👧👦") // true
"👩👩👧👦".contains("👩") // false
"👩👩👧👦".contains("u{200D}") // false
"👩👩👧👦".contains("👧") // false
"👩👩👧👦".contains("👦") // true
所以,斯威夫特说它包含自己(好)和一个男孩(好!)。但它随后表示,它不包含女人、女孩或零宽度细木工。这里发生了什么?为什么SWIFT知道它包含一个男孩,而不是一个女人或女孩?如果它将其视为单个字符并仅识别其包含自身,我可以理解,但它只有一个子组件而没有其他子组件的事实让我困惑。
如果我使用类似"👩".characters.first!
的内容,则不会更改此设置。
更令人困惑的是:
let manual = "u{1F469}u{200D}u{1F469}u{200D}u{1F467}u{200D}u{1F466}"
Array(manual.characters) // ["👩", "👩", "👧", "👦"]
即使我将ZWJ放在其中,它们也不会反映在字符数组中。接下来的事情有点说明问题:
manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true
所以我得到了与字符数组相同的行为...这非常烦人,因为我知道数组是什么样子的。
如果我使用类似"👩".characters.first!
的内容,这也不会改变。
推荐答案
这与String
类型在SWIFT中的工作方式以及contains(_:)
方法的工作方式有关。
👩👩👧👦是我们所熟知的表情符号序列,它呈现为字符串中的一个可见字符。该序列由Character
个对象组成,同时也由UnicodeScalar
个对象组成。
如果您检查字符串的字符计数,您将看到它由四个字符组成,而如果您检查Unicode标量计数,它将显示不同的结果:
print("👩👩👧👦".characters.count) // 4
print("👩👩👧👦".unicodeScalars.count) // 7
现在,如果您分析字符并打印它们,您将看到看起来像普通字符的东西,但实际上前三个字符在它们的UnicodeScalarView
中同时包含一个表情符号和一个零角连接符:
for char in "👩👩👧👦".characters {
print(char)
let scalars = String(char).unicodeScalars.map({ String($0.value, radix: 16) })
print(scalars)
}
// 👩
// ["1f469", "200d"]
// 👩
// ["1f469", "200d"]
// 👧
// ["1f467", "200d"]
// 👦
// ["1f466"]
如您所见,只有最后一个字符不包含零宽度连接符,因此在使用contains(_:)
方法时,它的工作方式与您预期的一样。由于您不是在与包含零角连接符的emoji进行比较,因此该方法将找不到除最后一个字符以外的任何字符。
String
,它由一个以零宽度连接符结尾的emoji字符组成,并将其传递给contains(_:)
方法,它的计算结果也将为false
。这与contains(_:)
与range(of:) != nil
完全相同,它试图找到与给定参数完全匹配的内容。由于以零宽度连接符结尾的字符形成一个不完整的序列,因此该方法尝试在将以零宽度连接符结尾的字符组合成一个完整序列时查找参数的匹配项。这意味着如果:,该方法将永远找不到匹配项
- 参数以零宽度连接符结束,
- 要分析的字符串不包含不完整的序列(即以零宽度连接符结尾,后面不跟兼容字符)。
演示:
let s = "u{1f469}u{200d}u{1f469}u{200d}u{1f467}u{200d}u{1f466}" // 👩👩👧👦
s.range(of: "u{1f469}u{200d}") != nil // false
s.range(of: "u{1f469}u{200d}u{1f469}") != nil // false
但是,由于比较只向前看,您可以通过向后查找字符串中的其他几个完整序列:
s.range(of: "u{1f466}") != nil // true
s.range(of: "u{1f467}u{200d}u{1f466}") != nil // true
s.range(of: "u{1f469}u{200d}u{1f467}u{200d}u{1f466}") != nil // true
// Same as the above:
s.contains("u{1f469}u{200d}u{1f467}u{200d}u{1f466}") // true
最简单的解决方案是为range(of:options:range:locale:)
方法提供特定的比较选项。选项String.CompareOptions.literal
对完全相同的字符执行比较。另外,这里所指的字符不是SWIFTCharacter
,而是实例和比较字符串的UTF-16表示形式--然而,由于String
不允许格式错误的UTF-16,这实质上等同于比较Unicode标量表示形式。
这里我重载了Foundation
方法,所以如果您需要原始的方法,请重命名此方法或其他名称:
extension String {
func contains(_ string: String) -> Bool {
return self.range(of: string, options: String.CompareOptions.literal) != nil
}
}
现在该方法对每个字符"应该"起作用,即使是不完整的序列也是如此:
s.contains("👩") // true
s.contains("👩u{200d}") // true
s.contains("u{200d}") // true
这篇关于为什么像👩;👩;👧;👦;这样的表情符号在SWIFT字符串中会被如此奇怪地对待?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!