美文网首页
1.7.Swift 3-String和NSString处理Uni

1.7.Swift 3-String和NSString处理Uni

作者: CDLOG | 来源:发表于2018-12-25 14:21 被阅读0次

    unicode长度是可变的。除了code unit的长度可变之外,unicode另外一个可变的特性,即组成同一个字符的code unit组合也是可变的。而区分StringNSString的一个重要方式,就是它们对unicode的这个特性的处理方式,是不同的。为了理解这个事情,我们从unicode grapheme clusters说起。

    Unicode grapheme clusters

    首先,我们定义一个字符串:

    let cafe = "Caf\u{00e9}"
    

    对于单词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}"
    
    
    StringInSwift

    尽管cafee的定义中貌似有5个字符,但实际显示出来的最后一个字符和之前用unicode scalar定义是一样的。我们管\u{0065}\u{0301}就叫做grapheme cluster。

    既然同一个unicode字符可以有多种表现形式,那么由不同code unit构成的字符串相等么?对此,Swift中的String和Objective-C中的NSString处理方式却是有差别的,这种差别,是区分它们最明显的地方之一。

    Canonically equivalent

    为了能识别上面cafecafee的情况,unicode规范中提出了一个概念:canonically equivalent。如何理解它呢?我们先来看Swift是如何识别cafecafee的。

    Swift String

    当我们要读取一个字符串中所有的字符时,可以访问String对象的characters属性:

    StringInSwift

    尽管cafee中最后一个字符的定义使用了两个code unit,Swift可以识别的Character中字符的个数也是4。

    但是,当我们查看cafecafee的UTF-8和UTF-16编码的个数时,就能看到它们的区别了:

    cafe.utf8.count
    cafee.utf8.count
    
    cafe.utf16.count
    cafee.utf16.count
    
    
    StringInSwift

    为什么会这样呢?我们用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
    
    
    StringInSwift

    这就是unicode canonically equivalent的含义,通过这些例子,你也能更好的了解到Swift在unicode表意正确上作出的努力。

    NSString

    而当我们把这些例子用在NSString上,情况就会有些不同。用同样的code unit定义下面两个NSString对象:

    let nsCafe =
        NSString(characters: [0x43, 0x61, 0x66, 0xe9], length: 4)
    nsCafe.length
    let nsCafee =
        NSString(characters: [0x43, 0x61, 0x66, 0x65, 0x0301], length: 5)
    nsCafee.length
    
    
    StringInSwift

    从图中可以看到,同样是使用不同的code unit构建字符串"Café",在NSString看来,它们是长度不同的两个字符串。

    因此,当我们比较nsCafensCafee的时候,结果也是没有意外的false

    nsCafe == nsCafee
    
    
    StringInSwift

    因此,==NSString来说,并没有执行canonically equivalent的语义。为了在不同的NSString对象之间进行语义比较,我们只能这样:

    let result = nsCafe.compare(nsCafee as String)
    result == ComparisonResult.orderedSame
    
    
    StringInSwift

    从图中可以看到,这样就能按照canonically equivalent的方式判断相等了。
    对于NSString Utf-8和Unt-16编码同一个字符串可能长度不同,那么比较结果一定不相等。对于String Utf-8和Unt-16编码同一个字符串可能长度不同。但是如果字面上看起来是一样的,那么比较结果就是一样的。(更加符合人的感觉,而不是编码方式判断)。

    Unicode 9.0?

    除了用两个code unit组合来实现一个字符之外,我们还可以使用多个code unit组合成一个“字符”,例如:

    é外围再套个圈:

    let circleCafee = cafee + "\u{20dd}"
    circleCafee.characters.count
    
    
    StringInSwift

    可以看到,尽管我们用3个code unit拼接了一个奇怪的字符,circleCafee的字符个数,仍旧是4。

    至此,事情看似一切正常。基于表达语意的计算方式也很符合人们的直觉。但事情并没有我们想象的这么简单,如果你挖掘一些emoji,就会发现一些有趣的现象:

    StringInSwift

    一个亚洲姑娘的字符数是2,而一群小伙伴的字符个数是4。为什么会这样呢?其实它们和字符é构成的原理是类似的。都是通过多个code unit组合而成的字符。但是,它们的字符计算方式却和我们之前看到的不太一样。

    你可以把4个小伙伴单独的字符都找出来,然后用\u{200d}把它们粘合起来,在Swift中就会看到,它们是相等的:

    StringInSwift

    甚至,如果你把中国和美国的国旗定义成一个字符串,然后统计字符串中字符个数的时候,好像Swift都不会在意后者的存在(LoL):

    StringInSwift

    因此,拼接字符é在语义上表达的一致性之外,后面我们举的这些例子,都是既有的unicode编码方式中还需要解决问题。在2016年6月更新的Unicode 9.0中,对于grapheme cluster的边界问题作出了修正,相信Swift 3在不久也会对上面的问题作出调整。

    相关文章

      网友评论

          本文标题:1.7.Swift 3-String和NSString处理Uni

          本文链接:https://www.haomeiwen.com/subject/uhxlkqtx.html