认真理解OC和Swift中的字符串
文档更新说明
- 最后更新 2019年06月19日
- 首次更新 2019年06月19日
前言
之前写过一篇OC中的NSString和Swift中的String , 简单记录了字符串数量的数量问题, 这次仔细说一下, 老的文档就不改了, 直接开一个新的.
如何正确处理逐个字符的问题
前面讲到, 在OC里面NSString的length表示存储字符的UFT16的单元数量, 如果我们的字符串全部是字母, 那么一个字母就表示1个单元, 但是字符串夹杂着汉字, emoji的时候就麻烦了, 麻烦的不是说汉字有多个字节存储, 相反汉字如果用UTF16存储的话,只需要1个单元即可(内存占用两个字节), 但是emoji不同, 有的需要2个UTF16单元, 有的甚至需要更多, 先来看下面代码:
1 | - (void)viewDidLoad { |
运行上面代码,结果如下:
1 | 9 |
可以看到其中emoji表情没有出现, 但是多了几个null这是为什么? 稍微修改一下上面的代码, 用纯OC的形式打印:
1 | - (void)viewDidLoad { |
运行上面代码, 结果如下:
1 | 9 |
现在可以看明白了, 原来符号😂
需要2个UFT16单元表示, 🥶
也是需要2个单元, 所以我们上面使用方法stringWithCharacters
的时候, 😂
需要两个单元才能解析出完整的符号,我们却只传了一个, 导致解析失败打印出来的就是null
.
理解了这个之后, 接着说正确处理的方法. OC提供了一个自动识别符号字节大小的方法, 修改一下代码如下:
1 | - (void)viewDidLoad { |
终于可以正确获取每一个符号了. 看到这里, 我们对OC字符串的处理的理解加深了, 平时产品经理让你限制用户名符号个数之类的处理起来也更明白了. 其实OC在这方面的处理还是不明显的, 下面看看Swift的处理方法, 顺便理解一下Swift为什么要这么设计.
Swift中String如何处理逐个字符?
第一次接触Swift的时候估计很多人都被其中的字符串API搞晕了, 它不像OC一样有一个- (unichar)characterAtIndex:(NSUInteger)index;
方法, index是整形, 传0就是第0个符号. 取而代之的是String.Index类, 下面先看一下遍历字符
1 | let str2 = "中国人😂|🥶a" |
运行上面代码, 结果如下
1 | 7 |
一个for循环可以直接遍历出人眼可见的符号, 而不像OC一样使用for i++语法遍历出的是单个UFT16单元, 这个确实可以避免OC中得到一半符号数据的尴尬.
再看一下, 如果得到指定位置的符号.
1 | print("index 0:", str2[str2.index(str2.startIndex, offsetBy: 0)]) |
重点说明, 上面的API就是很多人第一次接触Swift的时候感觉到的困惑, 为什么不直接用str2[0], str2[1]
这样的形式来取第一个第二个字符? 而把API复杂化. 从上面OC的处理方式我们知道, 直接使用数字获取某一个符号, 能否成功是跟数据的存储有很大关系, 为了让语法看起来更加清晰, Swift采取了Index的形式, 一个Index就代表一个完整单元, 用户不用担心数据完整性问题(我猜的, 实际上你要用str[0]表示第一个单元也可理解). 这里有个小窍门, 如果不考虑性能问题, 可以直接将String转为数组, 这样每个数组元素都是一个Character
, 就支持下标访问了.
深入一点, 如何获取字符串的其他表现形式呢? 像上面直接获取str2.count
得到的数量是7, 这个又和OC不同. Swift为字符串提供了多种不同视图, 看下面代码:
1 | print("count:", str2.count) |
要理解上面的结果, 我们需要知道每一个视图存储的内容是什么.
先从UFT8View
开始, 查看源码, 得到UTF8View
中存储的元素是public typealias Element = UTF8.CodeUnit
, 而UTF8.CodeUnit
又是public typealias CodeUnit = UInt8
, 所以UTF8View
里存的就是字符串的utf8编码, 每个元素都是一个Uint8, 0-255, 也就是一个字节, utf8编码中文占3字节, emoji大部分是4个字节,字母占1个字节, 9+8+2=19, 所以str2.utf8.count
为19, 下面代码使用String.UTF8View.Index
来获取指定index的单元
1 | print("index 0:", str2.utf8[str2.utf8.index(str2.utf8.startIndex, offsetBy: 0)]) |
其实我们有更简单的方法获取视图里的元素, 代码如下:
1 | let uft8Arr = Array(str2.utf8) |
其他视图, UTF16View
存储的元素类型是UInt16, 所以str2.utf16.count
为9, 和OC中length相同; UnicodeScalarView
存储的元素类型是Unicode.Scalar
, 可以理解为是一个Unicode编码单元, 一个单元对应一个人眼可见的符号, 所以str2.unicodeScalars.count
为7.
最后再看一下str2字符串中视图的全部数据,加深理解
1 | print(Array(str2)) |
让Swift字符串也支持整型下标访问
下面代码还是比较简单的, 扩展一下String类, 这样就支持便捷的整型下标访问了str[0]
, str[0..<str.count]
, str[0...1]
1 | // MARK: - 扩展系统的String类, 支持整型下标访问 |
总结
OC对于字符串的态度就是, 简单的一律使用UTF16来表示, 开发者想要更仔细处理字符, 需要调用更细致的API; Swift的设计则是一律使用人眼可见的符号表示, 想要更深入的处理字符串, 可以选择不同的视图, 这里仁者见仁智者见智, 你更喜欢哪种方式呢?