Why are emoji characters like ??????????? treated so strangely in Swift strings?
角色????????????????(有两个女人、一个女孩和一个男孩的家庭)编码如下:
所以它是非常有趣的编码;是单元测试的完美目标。然而,斯威夫特似乎不知道如何治疗它。我的意思是:
1 2 3 4 5 | "???????????".contains("???????????") // true "???????????".contains("??") // false "???????????".contains("\u{200D}") // false "???????????".contains("??") // false "???????????".contains("??") // true |
所以,斯威夫特说它包含自己(好)和一个男孩(好!)但是它说它不包含一个女人、女孩或零宽度的木匠。这里发生了什么?为什么斯威夫特知道里面有一个男孩而不是一个女人或女孩?我可以理解,如果它把它当作一个单独的角色,只认识到它包含自己,但是它有一个子组件,没有其他的,这一事实让我困惑。
如果我使用像
更令人困惑的是:
1 2 | let manual ="\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}" Array(manual.characters) // ["???","???","???","??"] |
即使我将zwjs放在其中,它们也不会反映在字符数组中。接下来是一个小问题:
1 2 3 | manual.contains("??") // false manual.contains("??") // false manual.contains("??") // true |
所以我得到了与字符数组相同的行为…这是非常恼人的,因为我知道数组是什么样子的。
如果我使用像
这与
"?"??????????????是一个emoji序列,它在字符串中呈现为一个可见字符。序列由
如果检查字符串的字符数,您将看到它由四个字符组成,而如果检查Unicode标量数,它将显示不同的结果:
1 2 | print("???????????".characters.count) // 4 print("???????????".unicodeScalars.count) // 7 |
现在,如果您解析并打印字符,您将看到看起来像普通字符的内容,但事实上,前三个字符在它们的
如您所见,只有最后一个字符不包含零宽度连接符,因此在使用
在此基础上,如果创建一个由以零宽度连接符结尾的emoji字符组成的
证明:
1 2 3 4 | 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 |
但是,由于比较只是向前看,您可以通过向后操作在字符串中找到其他几个完整的序列:
1 2 3 4 5 6 | 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 |
最简单的解决方案是为
这里我已经重载了
1 2 3 4 5 | extension String { func contains(_ string: String) -> Bool { return self.range(of: string, options: String.CompareOptions.literal) != nil } } |
现在,该方法对每个字符都"应该"工作,即使序列不完整:
1 2 3 | s.contains("??") // true s.contains("??\u{200d}") // true s.contains("\u{200d}") // true |
第一个问题是你用EDOCX1的0桥接桥接(SWIFT的EDOCX1 1)不是EDCOX1×2),所以这是EDCOX1×3的行为,我不相信ReaveStimes像SWIFT那样强有力地组成了EMOJI。也就是说,我认为Swift现在正在实现Unicode8,它还需要在Unicode10中对这种情况进行修改(因此,当它们实现Unicode10时,这一切都可能发生变化;我还没有深入研究它是否会发生变化)。
为了简化事情,让我们去掉基础,使用SWIFT,它提供了更为明确的视图。我们将从字符开始:
1 2 3 4 5 | "???????????".characters.forEach { print($0) } ??? ??? ??? ?? |
好啊。这正是我们所期望的。但这是个谎言。让我们看看那些角色到底是什么。
1 2 3 4 5 | "???????????".characters.forEach { print(String($0).unicodeScalars.map{$0}) } ["\u{0001F469}","\u{200D}"] ["\u{0001F469}","\u{200D}"] ["\u{0001F467}","\u{200D}"] ["\u{0001F466}"] |
啊……所以是
问题是,
1 2 3 4 | "???????????".unicodeScalars.contains("??") // true "???????????".unicodeScalars.contains("\u{200D}") // true "???????????".unicodeScalars.contains("??") // true "???????????".unicodeScalars.contains("??") // true |
当然,我们也可以寻找其中的实际特征:
1 | "???????????".characters.contains("??\u{200D}") // true |
(这严重重复了本·莱基罗的观点。我在注意到他回答之前把这个贴了出来。离开,以防任何人更清楚。)
斯威夫特似乎认为一个
1 | Array(manual.characters).map { $0.description.unicodeScalars } |
这将从LLDB打印以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 | ? 4 elements ? 0 : StringUnicodeScalarView("???") - 0 :"\u{0001F469}" - 1 :"\u{200D}" ? 1 : StringUnicodeScalarView("???") - 0 :"\u{0001F469}" - 1 :"\u{200D}" ? 2 : StringUnicodeScalarView("???") - 0 :"\u{0001F467}" - 1 :"\u{200D}" ? 3 : StringUnicodeScalarView("??") - 0 :"\u{0001F466}" |
此外,
1 | "\u{1112}\u{1161}\u{11AB}".contains("\u{1112}") // false |
这找不到
Swift 4.0更新
如SE-0163所述,string在swift 4更新中接收大量修订。本演示使用两个emoji表示两种不同的结构。两者都与一系列的表情符号结合在一起。
1。计数
在斯威夫特4。emoji被算作笔迹簇。每一个表情符号都算作1。Count属性也可直接用于字符串。所以你可以直接这样称呼它。
1 2 | "????".count // 1. Not available on swift 3 "???????????".count // 1. Not available on swift 3 |
在Swift4.0中,字符串的字符数组也被计算为图形簇,因此以下两个代码都打印1。这两个emoji是emoji序列的例子,其中几个emoji与它们之间的零宽度连接符
1 2 | "????".characters.count // 1. In swift 3, this prints 2 "???????????".characters.count // 1. In swift 3, this prints 4 |
在swift 4中,
1 2 | "????".unicodeScalars.count // 2. Combination of two emoji "???????????".unicodeScalars.count // 7. Combination of four emoji with joiner between them |
2。包含
在swift 4.0中,
1 2 3 4 5 6 7 | "????".contains("??") // true "????".contains("??") // true "???????????".contains("???????????") // true "???????????".contains("??") // true. In swift 3, this prints false "???????????".contains("\u{200D}") // false "???????????".contains("??") // true. In swift 3, this prints false "???????????".contains("??") // true |
其他的答案讨论了斯威夫特的所作所为,但不要详细说明原因。
你期待"A"吗?"等于"?"?我想你会的。
其中一个是带有合并器的字母,另一个是单个组合字符。您可以为一个基本字符添加许多不同的组合器,而一个人仍然认为它是一个单一的字符。为了处理这种差异,人们创建了一个笔迹的概念来表示一个人会认为一个字符是什么,而不管使用的是什么代码点。
现在,文本消息服务多年来一直将字符组合成图形符号
归根结底,如果您试图在图形级别使用它,那么应该将
如果您想检查它是否包含
我不知道Swift语法,所以这里有一些Perl 6,它对Unicode有类似的支持级别。(Perl6支持Unicode版本9,因此可能存在差异)
4我们往下走一层吧
1 2 3 4 5 6 7 | # look at it as a list of NFC codepoints my @components :="???????????".NFC; say @components.elems; # 7 say @components.grep("??".ord).Bool; # True say @components.grep("\x[200D]".ord).Bool; # True say @components.grep(0x200D).Bool; # True |
但是,降低到这个水平会使一些事情变得更困难。
1 2 3 | my @match ="???????????".ords; my $l = @match.elems; say @components.rotor( $l => 1-$l ).grep(@match).Bool; # True |
我认为Swift中的
例如,在这个级别上工作可以更容易地意外地在复合字符的中间拆分字符串。
您不经意间问的是,为什么这个更高级别的表示不能像较低级别的表示那样工作。答案当然是不应该的。
如果你问自己"为什么这件事必须如此复杂",答案当然是"人类"。
emojis与unicode标准非常相似,其复杂程度令人难以置信。肤色、性别、工作、人群、零宽度连接序列、标志(2个字符的Unicode)和其他复杂因素会使表情分析变得混乱。一棵圣诞树、一片披萨或一堆粪便都可以用一个Unicode代码点表示。更不用说,当引入新的emoji时,iOS支持和emoji发布之间会有延迟。不同版本的iOS支持不同版本的Unicode标准。
我已经研究了这些特性,并打开了一个源代码库,我是jkemoji的作者,帮助用emoji解析字符串。它使解析变得简单如下:
45
它通过定期刷新最新Unicode版本(最近为12.0)中所有已识别的emoji的本地数据库,并通过查看未识别emoji字符的位图表示,将它们与正在运行的OS版本中已识别的有效emoji交叉引用来实现这一点。
注释
之前的一个答案因为在我的图书馆做广告而被删除,没有明确说明我是作者。我再次承认这一点。