i 9 NSString和Unicode

作者: 纵横而乐 | 来源:发表于2016-03-27 21:51 被阅读1277次

    在当前这个时代(比如说公元2016年),如果你并不是在维护历史遗留的文本处理代码,没有在每个地方都使用Unicode的话,文本处理会出错。幸运的是Apple和NeXT促成了旨在将全世界所有字符都纳入同一种编码体系即Unicode标准的制定,也正是在1994年NeXT的foundation Kit成为了史上基于Unicode且面向任何编程语言的标准库。但即便NSString完全支持Unicode且提供了丰富的文本实用功能,处理数百种语言的文本仍然是一个相当棘手的事情,而且NSString的个中细节也是作为程序员的你需要仔细参详的。
    本文分为两部分,第一部分是关于Unicode编码,第二部分是关于Unicode下的NSString和使用NSString的时候会遇到的问题

    Unicode的前世今生

    众所周知,计算机是无法直接处理文本(即一串字符)的,它们只能处理数字。所以计算机中是使用一串数字来表示文本的,而实现数字到文本的映射即称之为编码。
    最为人熟知的编码即是ASCII码,它是一个7位的编码,将英文字母,数字,标点符号和控制字符映射到0到127之间的整数。随后出现了很多不同的8位编码以将除了英文之外的字符使用到计算机中,它们大多基于ASCII,即使用了未使用的第8位以编码其它的字母,符号或者整个字母表(西里尔字符和希腊字符)
    这些编码互相之间是不兼容的,而且8位空间仍然不足以承载即使是欧洲所使用的所有字符,更别说世界范围内其它的字符了。当时基于文本的计算机系统的一个问题是同一时刻只能使用一种编码(也称为code page)。如果在一台机器上写文本,在另一台使用不同编码页的机器上打开时,所有128到255对应的字符都会解析错误。
    东亚的字符比如中文,日文,朝鲜文还有另一个问题,那就是CJK所包含的字符如此之多,8位根本不够编码,所以催生了16位字符编码的出现。而一旦在处理不止一个字节能满足的值的时候,如何将它们存储在内存和磁盘上就不是一个小问题了。你需要根据字节序定义第二种映射规则,或者使用变长编码代替等宽编码。需要注意的是这第二种映射其实也是一种形式的编码,而我们用了同一个词“编码”其实是使大家对编码这一概念产生混淆的基本来源,下面讲UTF8和UTF16的时候会再进行讨论。
    现代操作系统大多都已经摆脱同一时刻只能使用一个code page的束缚了,所以只要每个文档正确地报告它的编码,处理数百种不同的编码都是可能的,不可能的是在同一个文档中使用多种编码,即在同一文档中用多种语言编写,而也就是这个原因为前Unicode时代的棺木订上了最后一个钉子。
    1987年开始,世界上主要的技术公司,包括苹果和NeXT开始一起开发一种容纳全世界所有文字系统的字符编码,并促成了Unicode标准1.0.0于1991年10月的发布。

    Unicode概览

    基本情况

    在基本层面上,Unicode标准为世界上几乎所有的印刷系统中所有字符和符号都定义了唯一的数值,每个这样的数据都称为code point,表示为U+xxxx,xxxx为4到6个16进制数。比如code point U+0041在拉丁字母表中代表A,而U+1F61B代表名为FACE WITH STUCK-OUT TONGUE的emoji 😛,需要注意的是字符的名字也是Unicode标准的一部分。可以用标准代码表或者使用OS X上的

    charater viewer 即图中的显示字符显示程序查找code point,在我10.10.5的系统上,未能在选中某个字符比如表情之后在右边显示其Unicode编码,可以通过右击表情“拷贝字符简介”并找地方粘贴出来以显示其编码。
    如上述提到的其它编码一样,Unicode只是抽象地描述了字符,定义了字符对应的值和名字等信息,但并没有规定它显示的glyph是怎样。比如对于中日朝文中都使用的某个汉字,Unicode定义了相同的code point但在这三种语言印刷系统中,这个汉字的显示glyph并不一定是相同的,因为它们是在各自国家的印刷系统中各自定义的。
    Unicode最初是16位的编码,可定义65536个字符,被认为可以容纳下世界范围内当今的所有字符,废弃及很少使用的字符被认为是需要放到Private use Areas的,即65536中的某一段保留区域U+E000–U+F8FF(当然,由于Unicode其实是一个21位的编码,所以还有BMP之外的两段private use area,即plane15和16中的U+F0000–U+FFFFD, U+100000–U+10FFFD),用户可以将这段区域的code point映射到自己定义的字符中,所以对于不同的定义,code point是会冲突的。Apple在这段区域中定义了一些符号和控制字符,虽然大多数都已经不推荐使用,一个著名的例外是apple的logo字符U+F8FF: ,如果你此刻并不是使用apple平台的机器,则这个字符的显示就会根据你机器的平台而显示一个完全不同的字符。
    Unicode编码空间随后就扩展成了21位,以容纳历史上使用或者很少使用的�日本汉字或者中文字符。这是一个很重要的点,而且之后即将介绍的NSString也与它很相关,那就是Unicode并不是16位编码。21位编码提供了1,114,112 code point。目前只使用了10%左右的空间,所以还有大把的code point可用。
    Unicode编码空间划分为17个65536空间,每个65536空间称为一个plane,Plane 0称为Basic Multilingual Plane (BMP),也是目前所使用的绝大多数字符所在的Plane,除了著名的emoji,其它的plane称为增补planes,且大部分都是空的。

    特有的Unicode特征

    其实将Unicode当成所有现存编码(大部分是8位的)的联合而不是一个universal code会更恰当,大多是为了兼容历史遗留的编码的原因,Unicode编码标准中包含了很多在处理Unicode字符串时需要注意的细微之处。

    结合字符序列

    为了与之前的编码兼容,有些字符不仅可以看成是单个code point,也可以看成是多个code point的序列。比如é既可以是precomposed 字符U+00E9 (LATIN SMALL LETTER E WITH ACUTE),也可以看成是分解的�形式U+0065 (LATIN SMALL LETTER E)在前U+0301 (COMBINING ACUTE ACCENT)在后。这两种形式是combining(or composite)character sequence中的变体,有这两种存在的原因其实是有些Unicode实现对precomposed字符glyph的处理好,而对分解形式的glyph可能无法辨认所造成,又或者有的Unicode实现相反,这种情况所造成的。结合字符不仅在西方出现在,在东方比如Hangul,音节가可以由单个code point(U+AC00) 表示,也可以由ᄀ + ᅡ (U+1100 U+1161)序列表示。
    在Unicode用语中,这两种形式并不是等价的,因为包含不同的code point,却是canonically equivalent:即它们有相同的形态(按道理是看起来应该是一样的,但实际是不是一样就得问上帝了)和意思。

    重复字符

    许多看起来相同的字符使用不同的code point编码了多次,且代表了不同的意思。比如拉丁字母A在形状上与西里尔字母A (U+0410)一样,但它们其实是不同的。将它们当成不同的code point不仅减轻了多遗留编码中转换的过程,也允许Unicode文本持有字符的意义。
    当然也有极少数的真正相等的重复,非营利性的Unicode标准及发展组织Unicode Consortium所列的 字母Å (LATIN CAPITAL LETTER A WITH RING ABOVE, U+00C5) 和字符 Å (ANGSTROM SIGN, U+212B),因为Ångström实际是瑞典语的大写字母,它们是完全相同的,在Unicode中它们不相等但是canonically相等。
    很多字符和序列则在广义上是重复的,在Unicode标准中称为 compatibility equivalence ,兼容序列代表相同的抽象字符,但可能没有相同的视觉体现或者行为。比如很多希腊字母,同时也用于数学了技术符号。罗马数字在标准拉丁字母之外额外编码在U+2160到 U+2183间。其它的兼容相等的例子是 ligatures:字符ff (LATIN SMALL LIGATURE FF, U+FB00) 与序列 ff (LATIN SMALL LETTER F + LATIN SMALL LETTER F, U+0066 U+0066)兼容,但不是canonically equivalent,虽然它们可能渲染得完全一样,但取决于不同的上下文,typeface及文本渲染系统的能力。

    Normalization形式

    已经看到Unicode中字符串相等并不是一个简单的概念,除了比较两个字符串,一个个code point的比较,还要比较canonical equivalence 和compatibility equivalence。Unicode为此提供了为所有相等的序列生成一个独一无二的code point序列的标准正交算法 。相等的标准既可以是canonical (NF) �或者 compatibility (NFK),四种Unicode的正交形式及获取此种正交形式的算法如下

    Unicode正交算法.png
    而NSString对上述4种正交算法的实现
    NSString正交化实例方法
    出于比较字符串的目的,既可以将它们全部正交化为分解(D)形式,也可以正交化为�组合(C)形式。通常分解形式更快,因为组合形式的算法包含两个步骤:字符会先分解然后重新进行组合。如果一个字符序列包含多个combining marks,combining marks的顺序在分解之后的顺序将是独一无二的。另一方面,Unicode Consortium推荐组合形式来进行存储以实现与历史遗留编码转换出来的字符串更为兼容。
    两种相等在做字符串比较的时候都很方便,尤其是在进行排序和搜索的时候。但需要记住的是,尽量不要用compatibility equivalence来正交化某个会永久存储的字符串。因为 it can alter the text’s meaning:

    Normalization Forms KC and KD must not be blindly applied to arbitrary text. Because they erase many formatting distinctions, they will prevent round-trip conversion to and from many legacy character sets, and unless supplanted by formatting markup, they may remove distinctions that are important to the semantics of the text. It is best to think of these Normalization Forms as being like uppercase or lowercase mappings: useful in certain contexts for identifying core meanings, but also performing modifications to the text that may not always be appropriate.

    Glyph变体
    一些fonts为单个字符提供了多个shape变体(glyphs),而且Unicode提供了称为 variation sequences 的机制以允许用户选择特定的变体。它的工作原理和combining字符序列挺像:一个由256个变体选择子(VS1-VS256, U+FE00 to U+FE0F, U+E0100 to U+E01EF)所跟随的基本字符。此标准还区分Standardized Variation Sequences(定义在Unicode标准中)和Ideographic Variation Sequences (由第三方提交到Unicode consortium并且一旦注册可以被任何人使用),从技术层面来说,这两者没有什么区别。standardized variation sequences的例子是emoji的style,很多emoji都有�表情和正常字符两种style,一个五颜六色的"emoji style"和一个黑白,更像符号的"text style"。比如 UMBRELLA WITH RAIN DROPS �字符 (U+2614) 看起来可以是: ☔️ (U+2614 U+FE0F) 或者: ☔︎ (U+2614 U+FE0E)

    Unicode Transformation Formats

    正如上面所看到的,将字符映射到code point仅仅只是第一步,还必须定义code point值在内存及磁盘上的存储形式。Unicode标准定义了几种并命名为UTF,大家普遍称之为编码,但由于它使用Unicode编码的字符并编码在UTF中,所以没必要区分这两步。

    UTF32

    每个code point使用32位,虽然简单,但空间使用太低效。

    UTF16和Surrogate Pairs(代理编码对)的概念

    UTF16更普遍也与NSString的Unicode实现更相关,它是用16位宽的术语称为code units来定义的。UTF16本身是变长编码,BMP中每个code point直接映射到一个code unit。由于BMP几乎涵盖了所有常用字符。其它plane中不常用的字符使用两上code unit编码。这种由两个code unit一起表示一个code point的方法是Surrogate Pairs。
    为避免UTF16编码字符串中模棱两可的字节序列,也为了使Surrogate Pairs的检测更简单,Unicode标准将U+D800 到 U+DFFF这个序列保留以供UTF16使用,这个序列中的code point永远不会分配字符。当应用在UTF16中看到这个range中的值时,它就知道它遭遇了Surrogate Pairs的一部分,实际的编码算法是很简单的, Wikipedia article for UTF-16可以看到更多。UTF16也是导致出现了看起来很奇怪的21位code point方案的原因,因为它最多能编码的值是U+10FFFF。它的做法是将BMP之外的code point减去0x10000,再将剩余的高10位加上D800-DBFF,低10位加上DC00-DFFFF,组成两个16位的序列。首先解决了通过21位Unicode字符的问题,其次,在解析UTF16时,根据每个code unit的值即可判断它是不是应当解析为字符,还是需要将其与其后的unit组成Surrogate Pairs。比如字符U+10437的code point为0001 0000 0100 0011 0111,UTF16值为1101 1000 0000 0001 1101 1100 0011 0111,UTF16 code units为D801 DC37。
    与所有多字节编码范式相同的是,UTF16(和UTF32)也必须考虑字节顺序。对于内存中的字符串,自然而然地会采用CPU所采用的端实现。对于存储在磁盘或者在网络上传输,UTF16也允许在字符串的开头插入Byte Order Mark (BOM)。BOM是一个code unit值为U+FEFF,通过检查文件的开头两个字节,解码器即可识别它的字节序。BOM是可选的,而且标准将大端字节序作为默认的选择。字节序这一项复杂性是其并没有广泛用于文件存储及网络传输格式的原因,虽然OSX和windows都将其作为内部使用。

    UTF8

    由于前256个Unicode code point与 通用的ISO-8859-1 (Latin 1) 编码相同,UTF16仍然对于英语及西欧文本来说浪费了很多空间,因为高8位基本都是0。也许更致命的是,对于遗留的ASCII编码的文本使用UTF16呈现起来是个严峻的挑战。 UTF-8 由Ken Thompson (of Unix fame) 和 Rob Pike 开发并修复了这些不足,这个设计很棒,而且强烈推荐阅读 Rob Pike’s account of how it was created
    UTF8使用1到4个字节编码一个code point。0到127的直接映射到一个字节(所以对于仅包含这些字符的文本,UTF8与ASCII编码是一样的)。接下来的1920个code point使用两个字节,其它所有的BMP中的code point需要3个字节,其它unicode plane中的code point使用4个字节。由于UTF8基于8位的code units,它不需要关心字节序(虽然有些程序在UTF8文件中添加了多余的BOM)
    空间效率和不需要关心字节序使UTF8成为了存储和交换unicode文本的最佳编码,它成为了事实上的文件格式,网络协议和web apis。

    NSString和Unicode

    NSString是完全建立在Unicode上的,但apple对它的解释是完全错误的,也即是 Apple’s documentation has to say about CFString
    objects
    中所说的

    Conceptually, a CFString object represents an array of Unicode characters (UniChar
    ) along with a count of the number of characters. … The [Unicode] standard defines a universal, uniform encoding scheme that is 16 bits per character.

    这是完全错误的,因为我们已经知道Unicode编码实际上是21位的,NSString的说明也同样误导

    A string object presents itself as an array of Unicode characters …. You can determine how many characters a string object contains with the length
    method and can retrieve a specific character with the characterAtIndex:
    method. These two “primitive” methods provide basic access to a string object.

    这份说明乍看起来好像是那么回事,但往深了去看,却发现characterAtIndex:方法的返回值是unichar,是一个16位的无符号整数,很显然,并不足以代表21位的Unicode字符。

    typedef unsigned short unichar;

    真相是NSString实际上代表的是一个UTF16 code unit的数组。相应地,length方法返回的是字符串中code unit数目。在NSString首次发布的时候是1994年,与Foundation Kit一起发布,那里Unicode仍是16位的编码,更大范围的Unicode和UTF16的surrogate character机制是在1996年Unicode 2.0中引入的。从今天的视角来看unichar和characterAtIndex:方法是很糟糕的,因为它会加深程序员对Unicode code point和UTF16 code units之间的误解。codeUnitAtIndex:将会是一个比characterAtIndex:好得多得多的方法名。
    如果关于NSString你只想记住一件事情,那么请记得NSString代表UTF16编码的文本。NSString的长度,索引及ranges都是基于UTF16 code units。基于这些概念的方法提供了不可靠的信息,除非你知道字符串的内容且做了适当的预防措施,无论何时文档中提到字符和unichar,都指的是code units。Apple文档在string programming guide中正确地描述了我们提到的概念但继续使用了错误的概念来使用character。墙裂建议阅读Characters and Grapheme Clusters,解释了这是怎么回事。
    虽然NSString概念上是基于UTF16的,但这并不意味着内部总是使用UTF16的数据。它也没有承诺过它的内部实现。事实上,CFString总是在 存储上尝试变得更高效,基于字符串的内容以及保持向UTF16 code units转换O(1)的复杂度。可以读下CFString source code

    常见的陷阱

    第一个使用Unicode字符序列创建字符串,默认情况下Cland希望源文件是UTF8编码的,只要确保Xcode使用UTF8存储文件,则可以向其中插入任何的Character Viewer中的字符。如果你更喜欢code point,可以输入@"\u266A"(♪) 直到U+FFFF �或者 @"\U0001F340"(🍀)等位于BMP之外的code point。有趣的是 C99并不允许把这些通用字符名用在标准C字符集中的字符上。所以下面这样会失败

    NSString *s = @"\u0041";
    // Latin capital letter A// error: character 'A' cannot be specified by a universal character name

    我觉得对于创建字符串变量的场景应该避免使用格式化标识%C,它需要unichar,很容易会在code unit和code points中产生混淆,但对log输出是有用的。

    长度

    -[NSString length]返回的是字符串中unichar的个数,而基于我们已经知道3个Unicode特性,这个值是可能与最终可见字符数不一样的。
    1 对于BMP之外的字符,比如emoji,原本一般只会遇到UTF16下一个code unit的BMP字符而很难遇到的surrogate pairs的情况,现在由于emoji的出现不得不考虑并恰当处理了。

    NSString *s = @"\U0001F30D";
    // earth globe emoji 🌍NSLog(@"The length of %@ is %lu", s, [s length]);
    // => The length of 🌍 is 2

    这个问题的解决方案是一个小hack,只需要简单地计算使用UTF32编码字符串需要的字节数然后除以4就可以了

    NSUInteger realLength =
    [s lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
    NSLog(@"The real length of %@ is %lu", s, realLength);
    // => The real length of 🌍 is 1

    2 combining字符序列:如果é编码为分解的形式,它是会当成两个code unit的

    NSString *s = @"e\u0301";
    // e + ´NSLog(@"The length of %@ is %lu", s, [s length]);
    // => The length of é is 2

    结果2在某种程度上是正确的,是因为字符串真的包含两个Unicode字符,但却与人眼观察到的可见长度不一样。可以使用方法precomposedStringWithCanonicalMapping将字符串正交化并得到正交化形式的C (precomposed characters)来获取更好的结果:

    NSString *n = [s precomposedStringWithCanonicalMapping];
    NSLog(@"The length of %@ is %lu", n, [n length]);
    // => The length of é is 1

    但可惜的是这并不能应对所有的情况,因为�只有大多数常见的combining字符序列于precomposed form 可用,基础字符和其它combining marks的结合仍会是和decomposed form一样,就算是正交化之后。如果真想知道肉眼可见长度,则需要自己手动遍历字符串并计算。可参阅下面的Looping部分
    1.Variation sequences: These behave like decomposed combining character sequences, so the variation selector counts as a separate character.(这段文字还不知道为什么会出现在原文中)

    随机访问

    直接通过characterAtIndex:获取的索引访问unichar有同样的问题,因为字符串可能包含combining字符序列,surrogate pairs或者Variation sequences。苹果用composed character sequence这个术语指代所有这些特征。这个术语很容易混淆,别把苹果的术语composed character sequences 与Unicode术语combining character sequences混淆了,后者只是前者的一部分。用rangeOfComposedCharacterSequenceAtIndex:
    方法找出给定位置的unichar是否是代表单个字符(当然它可以包含多个code points)的code units序列的一部分。任何时候需要将字符串的range中的未知内容传递给另一个方法的时候,为确保Unicode字符没有被撕开,都需要做这个操作。

    Looping

    通过rangeOfComposedCharacterSequenceAtIndex:方法,可以正确地遍历字符串中所有字符,但每次需要遍历字符串时都这样做会很不方便。幸运的是NSString提供了enumerateSubstringsInRange:options:usingBlock:
    方法,这个方法帮你隐藏起了Unicode的特性,提供了遍历字符串中字符序列,词,行,句子及段落的便捷方法。甚至可以通过传入NSStringEnumerationLocalized选项,将用户当前的locale也作为判定断词和断句的依据之一。遍历字符可以使用NSStringEnumerationByComposedCharacterSequences选项:

    NSString *s = @"The weather on \U0001F30D is \U0001F31E today.";
    // The weather on 🌍 is 🌞 today.NSRange fullRange = NSMakeRange(0, [s length]);
    [s enumerateSubstringsInRange:fullRange
    options:NSStringEnumerationByComposedCharacterSequences usingBlock:
    ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
    { NSLog(@"%@ %@", substring, NSStringFromRange(substringRange));}
    ];

    从这个棒到乖乖的方法也可以看出Apple希望我们将字符串当作子字符串的集合来对待,而不是当做字符的集合。因为一个unichar并不足以表示一个Unicode字符,而且有些字符是由多个Unicode code point组成的。这个方法是相比来说最近才添加的(in OS X 10.6 and iOS 4.0),之前遍历字符串中的字符可没这么方便

    比较

    字符串对象本身是并非正交的,除非手动转换,这也意味着比较的字符串中如果带有combining字符序列的字符,比较结果将会出错。因为 isEqual:
    isEqualToString:
    都是一个字节一个字节地比较字符串的。如果想用�组合的和分解的字符串变体用于比较,必须将字符串先正交化:

    NSString *s = @"\u00E9"; // é
    NSString *t = @"e\u0301"; // e + ´
    BOOL isEqual = [s isEqualToString:t];
    NSLog(@"%@ is %@ to %@", s, isEqual ? @"equal" : @"not equal", t);
    // => é is not equal to é
    // Normalizing to form C
    NSString *sNorm = [s precomposedStringWithCanonicalMapping];
    NSString *tNorm = [t precomposedStringWithCanonicalMapping];
    BOOL isEqualNorm = [sNorm isEqualToString:tNorm];
    NSLog(@"%@ is %@ to %@", sNorm, isEqualNorm ? @"equal" : @"not equal", tNorm);
    // => é is equal to é

    当然还有其它选择,那就是用 compare:
    方法或者像localizedCompare:
    这样compare:的变体方法,会返回使用compatibility equivalent版本字符串的匹配结果。但这一点Apple并没有仔细注明,但需要注意的是通常大家在使用过程中希望的匹配是canonical equivalence,但compare:方法并没有给这个选项

    NSString *s = @"ff";
    // ffNSString *t = @"\uFB00";
    // ff ligatureNSComparisonResult result = [s localizedCompare:t];
    NSLog(@"%@ is %@ to %@", s, result == NSOrderedSame ? @"equal" : @"not equal", t);
    // => ff is equal to ff

    如果你需要用compare:方法但不想考虑equivalence,compare:options:
    这个方法可以指定NSLiteralSearch,也可以加速匹配,就是说连compatibility equivalent相等的也可能会判定为不相等。

    从文件或者网络读取文本

    一般来讲,只有在你知道一段文本的编码的时候这段文本数据才有用,而当你从网络上下载了一段文本数据的时候,你通常是已经事先知道了它的编码,或者可以直接从HTTP中获取到。随后用 -[NSString initWithData:encoding:]
    方法就可以水到渠成地完成NSString的创建了。
    然而文本文件本身并没有把它的编码写在文本文件中,但NSString通常可以通过查看文件的扩展属性或者使用启发式逻辑(heuristics)(比如特定的二进制序列绝对不会出现在有效的UTF8文件中)来获取文件的编码方式。为用已知的编码格式从文件中读取文本,使用[NSString initWithContentsOfURL:encoding:error:]
    。而读取未知编码格式的文件,Apple提供了这个指引

    如果强制去猜测编码方式:

    1. 试着使用stringWithContentsOfFile:usedEncoding:error:
      或者initWithContentsOfFile:usedEncoding:error:
      (或者基于URL的等效方法),这些方法会试着判定资源的编码方式,如果成功会返回编码方式的引用
    2. 如果第1步失败,试着指定UTF8作为编码方式读取资源
    3. 如果第2步失败,尝试一种合适的遗留编码。
      这里的合适指的是依赖于当前所使用的环境,�可能是默认的 C 字符串编码, 可能是 ISO 或者 Windows Latin 1, �或者其它�,取决于数据来源于哪里
    4. 最终可以尝试使用NSAttributedString的加载方法,比如initWithURL:options:documentAttributes:error:等。
      这些方法尝试尝试加载纯文本文件并返回使用的编码,如果你的应用对文本没有特别的需求的话,�这些方法可以用在某种程度上来说任意的文本文档上。但它们可能对于系统级工具或者非自然语言的文本并不合适。

    写文本到文件中

    之前已经提到,除非有特别的说明,否则纯文本文件的编码,网络传输或者你自己的文件格式或者网络协议都应该使用UTF8。写字符串到文件中用writeToURL:​atomically:​encoding:​error:

    这个方法会自动为UTF16或者UTF32编码的文件添加字节序的标记。它也会在文件扩展信息中使用com.apple.TextEncoding属性名添加文件编码信息。由于initWithContentsOf…:usedEncoding:error:方法显然知道这个属性,使用标准的NSString方法可以确保你在从文件中加载文本的时候使用正确的编码。

    结语

    文本是很复杂的,虽然Unicode已经大大方便了处理文本的过程,但并不能免除程序员了解它的工作原理的工作量。当今的很多app实际都需要处理多门语言的文本。即使你的app并不是localized到中文或者阿拉伯文,只要你支持任何文本的输入,都必须准备处理所有的Unicode字符。
    你需要用当前世界的所有语言输入来测试你的app,并确保在单元测试中使用尽可能多的emoji和非拉丁文本,如果你不能轻松地获取特定各类的文本,可以上Wikipedia在 Wikipedia of your choice中从任意文章中复制单词即可。

    扩展阅读

    Joel Spolsky: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets. This is more than 10 years old and not specific to Cocoa, but still a very good overview.
    Ross Carter gave a wonderful talk at NSConference 2012 titled You too can speak Unicode. It’s a very entertaining talk and I highly recommend watching it. I based part of this article on Ross’s presentation. Scotty from NSConference was kind enough to make the video available to all objc.io readers. Thanks!
    The Wikipedia article on Unicode is great.
    unicode.org, the website of the Unicode Consortium, not only has the full standard and code chart references, but also a wealth of other interesting information. The extensive FAQ section is excellent.

    相关文章

      网友评论

      • 无星灬:请问一下。。。NSString *s = @"qwe" 想把这个s转成Unicode码方法是啥啊、、、
        来来来小牛仔:大佬,想知道怎么转换,NSString *str = @“e617”;
        怎么能转成 str = @"\U0000e617";
        来来来小牛仔:同求,怎么把s转成nssting类型的unicode码

      本文标题:i 9 NSString和Unicode

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