swift 基础学习。
swift 字符串 字符 Unicode 标量 码位 UTF-16 UTF-8编码。 OC字符串swift字符串,NSRange Range lenght count 介绍。
Swift 的 String 类型是 Unicode 标量(UnicodeScalar)的集合。可以抽象的理解为在swift 里面 虽然我们肉眼看的字符串是一个个字符。但是底层存的时候理解为一个数组 就是集合。里面装的是一个个Unicode 标量(UnicodeScalar)。
Unicode : 是字符和数字(码位)的映射。其实Unicode概念很弱。他只是一个抽象概念。是指我们肉眼看的字符串 再计算机底层存储的时候是利用Unicode字符表来完成的。
Unicode字符表、Unicode码位、Unicode标量(UnicodeScalar):就是我们肉眼看的每一个字符 都在这个Unicode表里面。每一个字符都对应一个数字。这个数字的值我们就称之为Unicode 码位。Unicode 标量其实概念性质和码位一样 都是代表这个字符再Unicode字符表里面对应的东西。理解标量为把 这个码位包装一层。类型swift我们常见的\u{0001F468}就是swift 字符串的一个标量。包了一层。 但是里面的具体值还是代表码位。所以从一定意义上来说 这俩可以为一个东西。
字符:我们把肉眼看到的一个单一的视觉内容称之为一个字符。不管在OC还是swift里面都是肉眼看到的一个内容为一个字符,为什么这么说呢。如果只是我们常见的普通的字符串 比如 只有中文 英文 等内容的字符串 eg: "123ydj大王" 这类的 我们就可以理解为 1 y 大 这都是一个字符。有些特殊字符 比如表情 "👪" oc和swfit里面 也是把 "👪" 成为一个字符。你可能说 这不就是一个整体吗。自然为一个字符。但是后面就介绍特殊之处了。
"123ydj大王" 这类字符串 1 2 3 y d j 大 王 这几个字符串再 Unicode符号表里面都对应这个码位 一个标量表示。但是 "👪"这类字符却不是一个标量表示的。他由多个标量表示是。一般为2个Unicode标量表示。
代码层面就是 我们swift 可以 这样来写。 let combinedEAcute = "\u{65}\u{301}" let eAcute = "\u{E9}" 这俩字符串显示的都是"é" 我们把 \u{65}\u{301} 和 \u{E9} 都称之为 一个字符。 但是很明显 一个是俩Unicode标量 一个是一个Unicode标量搞出来的。
理解为 我们 swift 字符串 虽然肉眼看的的是一个字符,但是转为计算机存储的时候 可能存的是 多个Unicode标量 。底层存的是 从符号表读出来的码位。标量。这类多个标量组成一个字符的 我们称之为可扩展的字形群集。
就是 我们swift 定义一个字符串 let abc = "é" 在计算机内存上其实存的是"\u{65}\u{301}"俩东西(本例子不对,只是用来具象化理解的)。
OC同样是支持Unicode的 也是基于Unicode符号表的。 只是写法不一样 OC 的Unicode符号写法是 \u0060.并且OC 里面的 Unicode 是ASCII字符 直接使用 字符就可以,不要再写成Unicode 编码样子了。 就是说OC 里面 只有一些特殊表情 字符,重音符等可以用 \u0060 Unicode形式,其他都直接用原来的字符。
为什么介绍这么多Unicode相关知识, 因为它是基础, 后续的API都是从这基础去理解的才可以理解透彻。
// 字符串 内存理解。 我们定义的字符串对象。他最终在内存中存的就是字符串对应的Unicode码位。并不是
Unicode标量转为UTF-16后的内容。具象化理解就是 比如 let str = "abc"
str 字符串内容 a b c 三个字符。比如其对应的Unicode码位为 32 43 15 。 将其转为UTF-16 可能为 /x0012 /x0033 /x0015 。我们在内存上存的是 32 43 15 并不是转为UTF-16后的数字(为什么是数字 因为计算机只知道01数字,Unicode符号表最后对应的码位都是二进制数)。
接下来看 UTF-8 UTF-16 的概念。
UTF-8 UTF-16编码 是指 对字符转为Unicode标量后的值 进行再一次转换。 不管UTF-8还是UTF-16编码 编码之后都是以16进制数来表示的。例如A 变为Unicode 是59。 UTF-8编码后为 68 UTF-16编码后为 3B。 68 3B都是16进制的。 以上都是假的数据。举例子而已。
UTF-8的转换为变长的。比如我们 一个 ‘A’字符 对应Unicode码位为 59。经过UTF-8转换后仍然是一个数。并且这个数是一个字节的两个16进制数表示。一些特殊字符,中文 表情 可能就是存的2个或者多个16进制数。
简单的字符 转为Unicode的时候 一般情况下也是一个的Unicode标量。一个Unicode经过 UTF-8 UTF-16转换后 都是普通的两个16进制数 1字节大小。比如ASSIH里面的字符。但是也会有很特殊的情况。
比如一个表情字符他可能由多个Unicode标量组成。并且再经过 UTF-8和UTF-16转换的时候。我们知道UTF-8和UTF-16编码是对Unicode标量编码的。并不是一个Unicode标量经过UTF-8和UTF-16编码就会固定编码为2个16进制数。UTF-8和UTF-16在对Unicode标量编码的时候会针对Unicode标量的值 也就是码位进行区别对待。有一些值大的Unicode标量,再经过UTF-8和UTF-16编码之后会用两个三个甚至四个字节 也就是2个三个甚至四个 2个16进制数代表, 比如"👨👩👧👦"表情它就是由7个Unicode标量组成。这里可以看做四个小表情组成一个大表情,每个表情是四个表情Unicode标量 + 3个链接符Unicode标量。 而 这四个小表情Unicode标量就是特殊标量。它再转为UTF-8和UTF-16编码的时候 仅仅用一个字节 2个十六进制数是表示不下的。比如UTF-8编码。它就需要用四组每组两个16进制数去表示(总之就是多个16进制去代表一个unicode)。而UTF-16编码 正常是 2个十六进制代码正常的Unicode标量。但是遇到以上特殊字符的unicode 就要用满8个十六进制数,两组/x1245 /x4621 称之为代理对。 代理对 理解 就是 正常4个十六进制数称之为编码的一个码元。 正常情况下 UTF-16编码对常用字符Unicode 编码之后 只用了 2个十六进制,但是 其实UTF-16编码它编码之后使用4个十六进制空间存储。正常的字符Unicode 只是用到了 2个十六进制数,就是1字节。其他空间 空着为0。如果是特殊字符 就要用到空下空间。用2字节表示。成为代理对表示是。
以上介绍的Unicode 标量 码元值。我们普通的一些 ASSIH的字符。以及一些普通的中文字符 标点符号等 都是正常的字符串。都是适用于对应一个Unicode标量。转为UTF-8 UTF-16都是用一个字节 2个十六进制表示了。区别就是 UTF-8真的就是一字节大小,但是UTF-16确实 2个字节大小,只是另外一个字节空着为0。针对一些表情字符,古代中文。重音符等字符。他们可能也是由一个Unicode标量表示,但是你这标量的码值会非常大。我们把上面正常的字符成为
基本多语言平面字符(BBM),码位较大的字符我们称之为辅助平面字符。
UTF-8编码就是把一个unicode 标量 经过UTF-8规则转为 一个字节 到四个字节不等的16进制数的值。他是一直变长的。两个16进制数 就是UTF-8的编码单元。具象化就是 我们一个字符。经过UTF-8编码。省略Unicode过程就是 字符会变化为 /x39 这种。特殊的字符的时候 比如中文 他可能就是 /x23 /x34 俩个。他就是2字节。 可有可能三个 四个。
UTF-16编码 同理 也是把一个unicode 标量 经过UTF-16规则转为 两个字节的 16进制数。比如一个字符。经过UTF-16转换。他可能是 /x13 .其实他是/x0013的 00 是省略了。没占用也留着。保持了定长的编码单元,没用上也要留着位置。但是遇到特殊的表情字符。就是辅助平面字符的时候。/x0012 四位16进制数 不够表示表情字符。那么就需要代理对啦。就需要两组 /x0012 /x0013 来一起表示表情特殊Unicode标量了。 这里隐藏了一层逻辑,就是unicode标量。将他填进去 就多一层了。一个字符 可能多个unicode标量。一个大的值的标量。由UTF-16编码需要代理对 两组/x0012 /x0013编码单元组成。 这里 /x0012就是UTF-16编码单元。而UTF-8编码单元就是指/x13 。 代理对概念 /x0012 /x0013 这里 00 将不在是00 而是具体数,我们称之为高位代理。后面的12 13 为地位代理。 都有具体含义, 这里不追究那么深。
再iOS系统 手机和mac系统上 OC 采用的是UTF-16编码格式存储字符串。swift也是采用UTF-16编码格式存储字符串。
UTF-16 on Apple Platforms: 在 macOS 和 iOS 等 Apple 平台上,CFString 内部通常使用 UTF-16 编码。String 的底层实现采用了 UTF-8 编码(以及一些其他优化)
UTF-8 on Linux: 在 Linux 等非 Apple 平台上,CFString 内部可能使用 UTF-8 编码。
OC NSsting 字符串 编码方式默认是使用UTF-16编码格式的UTF-16是定长编码单元的。每个Unicode都对应着一个一个UTF-16编码单元。不会变化 总之是2字节。虽然有些特殊的表情unicode占用两个UTF-16编码单元 ,4个字节,但是此时可以理解为 两个unicode的处理结果 4个字节分为2个2字节。
Swift String 字符串的编码方式 再swfit 4之后 UTF-16还是UTF-8 不确定。内部优化。
以上结果 就是 说 再字符串 传递以及处理的时候 都是经过编码后处理的。我们看OC 字符串。
||||||||||||| 每一段都是两字节,都可以理解为一个unicode我们在处理的时候。针对内存处理的时候 只需要2字节 2字节 去处理就可以啦。(那种辅助平面字符其实是两个||| 也是2个2字节。不会造成内容错乱,一会会特殊介绍)
而swfit string
||||__||||||__|____| 内存上是这种的。 没办法直接内存以2为倍数处理。
OC 的字符串 有length属性。 这个属性 就是 字符串 经过UTF-16转码后 UTF-16具有码位的个数。 码位就是|__|。
swift 只有 count 而count 并不是 指UTF-16编码之后的个数。 而是 字符串具有的unicode 个数。他和length 是不相等的 很多数据。尤其有表情字符的时候。
OC用UTF-16编码 弱化了Unicode 。是继承CString 。处理字符串的时候都是基于内存去处理的。很多API都是直接给index 是int 类型的或者是NSRange 类型的
就是利用了 内存都是一段一段相等的原理。 直接操作内存去替换移除 增删改查的。
说到NSRange API是 location 和legth 也都是 int 类型。都是服务于UTF-16OC字符串的都是基于内存处理的。 再富文本处理的时候也是。SNRange都是int 就是|||||||||||||这里第几段开始的位置,length就是几段长度。
所以OC在处理之前说的辅助文本的时候 有可能会出现问题 就把 表情字符串 截烂了。 也是需要注意计算length 和start
而swfit 呢 。 着重考虑了 Unicode 标量的概念。 底层使用UTF-8优化了。他的内存上 不是定长的。 没办法再使用index int 类型和长度来找值了。
swift sting API
dropLast
// 字符串 处理的时候 用到的NSRange 参数 的location 和length 说的都是字符串UTF-16编码之后的位置的长度。 正常普通字符串没啥感觉。因为UTF-16编码之后的个数和字符串看着一样。比如 "123456wda", location 0 length 2 都是指 "123"这个子字符串。 但是遇到特殊字符串尤其表情字符。在深点说 就是辅助平面字符💖/或者是复合组成字符👨👩👧👦,需要代理对表示的高位字符"é"。等这类字符 。他们在转为UTF-16的时候就不是1:1的了。比如 "12👨👩👧👦34" 我们知道location 0 是 1的位置 但是location 3的位置可不是👨👩👧👦 它在底层变成了多个UTF-16编码。/x2133 /x1232 /x2133 /x1232 /x2133 /x1232 /x2133 /x1232 我记得是 是有 11个呢。代表长度11个呢。而3的位置 只是 👨👩👧👦它转的第一个/x2133的位置。如果我们按照正常思想去构造range的时候 比如把一个字符前五个字符截取了。那么如果你只是简单的location 给5 是错误的。他会把👨👩👧👦字符串砍断。会出现意想不到的结果。
//处理字符串的时候 我们OC 有一个length 字段。能获取一个字符串总utf-16长度。一般场景为 高亮 字体大小不同的富文本的时候会遇到字符串处理的情况。我们处理的时候 一般可以先考虑以下手段。1.如果是简单的富文本高亮什么的。我们可以 一段一段富文本 的设置,最后append 起来。也可以考虑利用字符串API 查找出需要高亮的字符range 。最后再高亮。如果以上都解决不了。最后的方法可以考虑便利字符串。取每个字符然后去取UTF-16.count 算做length 和location 。 这样会避免复杂字符串被处理的时候 被截断 或者是这只富文本设置的不对问题。
/// 同时 系统还提供了一些 API 更好的处理表情字符串 防止被切割掉。或者富文本位置不对。比如我们得到一个位置 可以用一个API 获取此时这个位置是否是特殊表情位置中,比如 dsad.rangeOfComposedCharacterSequences(for: <#T##RangeExpression#>)
dsad.rangeOfComposedCharacterSequence(at: <#T##String.Index#>)
//这俩方法 可以把 位置 或者range 转为 没有切割掉复杂表情的位置和range。 dsad.rangeOfComposedCharacterSequence(at: <#T##String.Index#>) 这个方法返回一个Range 再swfit 里面 string.Index就代表的是一个字符的位置了。不想OC 的int 可能是一个表情字符中的位置。所以 swfit 的这个API 主要功能是获取 这个字符的range startIndex..<endIndex . OC更好用一些。OC 可以直接根据自己给的一个值。获取到另外一个location 。最后截取的时候 不至于把表情字符串截断。
// 在字符串处理 或者是字符串高亮这块处理字符串的时候。 如果有UTF-16位置的前提下去处理字符串的时候。需要考虑表情字符串被截断的问题。或者是表情字符可能很长的问题。一个表情字符串可能是11length 。 在处理的时候 可以多考虑OC的方向。因为swfit的API都倾向于字符单位的或者是unicode标量的单位。但是OC字符串处理的时候 就需要考虑表情字符串被截断的问题。
swift 字符串 一般的API 都是基于字符处理, 如果是基于unicode 处理 也需要考虑表情字符被截断的问题。
说一下富文本处理。 因为富文本处理 接受的API 参数 都是NSRange 我们介绍了 NSRange 接受的是UTF-16的个数和count。 所以我们可以直接构造NSRange对象。 也可以利用swiftAPI 构造 Range 之后 用 NSRange(ac,in: originStr) 来获取NSRange 。 这样有一个好处就是 swift的Range 并不会把表情字符串给切割了的问题。 意思就是 Range 这个对象 只要构造出来 , 就不会像NSRange 那样 , 可能location 是 表情字符中的某一位置。