一:swift字符串特性
个人理解Unicode是比较符合人的思维而设计的,一串复杂的字符,一眼看上去是几个长度(由几块组成),和实际上描述这个Unicode的数据长度完全没关系.因为对编程语言来说,Unicode的内在复杂度究极折磨.
大部分语言的字符串类型可以存储Unicode 数据 ,但是它没有保证像是获取字符串⻓度这类简单操作会返回 “恰当” 的结果,而swift就在致力于实现这种Unicode正确性.
Swift 中的 String是 Character 值的集合,而 Character 是人类在阅读文字时所理解的单个字符,这与该字符由多少个 Unicode 码点组成无关,也就是前面说的即使是非常复杂的字符串,一眼看起来是几块,swift会像人一样去分成几个Character.
那么代价是什么呢.
代价就是swift字符串的复杂度上升了(悲),str[999] 不能获取字符串的第一千个字符;str[idx+1] 不能访问到下一个字符;当字符拥有可变宽度时,字符串并不知道第 n 个字符到底存储在哪儿,它必须查看这个字符前面的所有字符,才能最终确定对象字符的存储位置,所以这不是一个 O(1) 操作.
今天的 Unicode 是一个可变⻓格式。它的可变⻓特性有两种不同的意义:由编码单元(code unit) 组成 Unicode 标量 (Unicode scalar);由 Unicode 标量组成字符.
Unicode 数据可以被编码成许多不同宽度的编码单元,最普遍的使用的是 8 比特 (UTF-8) 或者16 比特 (UTF-16) 。UTF-8 额外的优势在于可以向后兼容 8 比特的 ASCII。这也使其超过 ASCII成为网上最流行的编码方式。Swift 将 UTF-16 和 UTF-8 的编码单元分别用 UInt16 和 UInt8 来表示.
标量在 Swift 字符串字面量中以 "\u{xxxx}" 来表示,其中的 xxxx 是十六进制的数字。比如欧元符号 € 在 Swift 中写作 "\u{20AC}"。Unicode 标量在Swift 中对应的类型是 Unicode.Scalar,它是一个对 UInt32 的封装类型.
总结来说:
用户所认为的在屏幕上显示的 “单个字符” 可能仍需要由多个编码点组合而成。在 Unicode 中,这种从用户视⻆看到的字符有一个术语,它叫做扩展字位簇.
如何用标量来形成字位簇的规则,将决定字符文本是如何分段的。比如说,让你敲击键盘上的退格键时,你期望的是文本编辑器删除掉一个字位簇。这个 “字符” 有可能是由多个 Unicode标量组成的,每个标量在文本表示的内存存储中,又可能使用了可变数量的编码单元。在 Swift中,字位簇由 Character 类型进行表示,这个类型可以对任意数量的标量进行编码,并形成一个从用户⻆度来看的字符.
1.字位簇
Unicode 将U+00E9 (带尖音符的小写拉丁字⺟ e) 定义成一个单一值。不过你也可以用一个普通的字⺟ “e”后面跟一个 U+0301 (组合尖音符) 来表达它。这两种写法都显示为 é,而且用户多半也对两个都显示为 “résumé” 的字符串彼此相等且含有六个字符有着合理的预期,而不管里面的两个“é” 是由哪种方式生成的。Unicode 规范将此称作标准等价.这种特性非常人性化.
let single = "Pok\u{00E9}mon" // Pokémon
let double = "Poke\u{0301}mon" // Pokémon
single.count // 7
double.count // 7
single == double // true
single.utf16.count // 7
double.utf16.count // 8
(single as NSString).isEqual(to: double)// false
按照这两个字符串在swift中,既可以是相等,又可以是不等.
在很多其他语言中,含有颜文字的字符串也令人有些惊讶。很多颜文字是由无法存放在单个UTF-16 编码单元的 Unicode 标量来表示的,比如在 Java 或者 C# 里,会认为 "" 是两个“字符” ⻓,但是swift可以正确的处理.
let oneEmoji = "" // U+1F602
oneEmoji.count // 1
2.字符串和集合
String 是 Character 值的集合,并且遵循Collection协议,这点在Swift2,3,4里来回修改,主要是因为有时候String不能很好的符合集合的特性,例如将两个集合连接的时候,你可能会假设所得到的集合的⻓度是两个用来连接的集合⻓度之和。但是对于字符串来说,如果第一个集合的末尾和第二个集合的开头能够形成一个字位簇的话,它们就不再相等.
因此,String 并不是一个可以随机访问的集合。就算知道给定字符串中第 n 个字符的位置,也并不会对计算这个字符之前有多少个 Unicode 标量有任何帮助。所以,String 只实现了 BidirectionalCollection。你可以从字符串的头或者尾开始,向后或者向前移动,代码会察看毗邻字符的组合,跳过正确的字节数。不管怎样,你每次只能迭代一个字符.
String没有实现MutableCollection,所以没有set下标方法,当需要修改字符串的时候需要使用replaceSubrange
var greeting = "Hello, world!"
if let comma = greeting.firstIndex(of: ",") {
greeting.replaceSubrange(comma..., with: " again.")
}
greeting // Hello again.
3.字符串索引和子字符串**
String.Index 是 String 和它的视图所使用的索引类型,它本质上是一个存储了从字符串开头的字节偏移量的不透明值.很多看起来很简单的任务,比如说要提取字符串的前四个字符,实现看起来都会有些奇怪.
不过可以使用Collection的接口
s[..
和所有集合类型一样,String 有一个特定的 SubSequence 类型,它就是 Substring。Substring 和 ArraySlice 很相似:它是一个以不同起始和结束索引的对原字符串的切片。子字符串和原字符串共享文本存储,这带来的巨大的好处,它让对字符串切片成为了非常高效的操作.
在你对一个 (可能会很⻓的) 字符串进行迭代并提取它的各个部分的循环中,切片的高效特性就非常重要了。这类任务可能包括在文本中寻找某个单词出现的所有位置,或者解析一个 CSV 文件等。在这里,字符串分割是一个很有用的操作。Colleciton 定义了一个 split 方法,它会返回一个子序列的数组 (也就是 [Substring])。最常用的一种形式是
extension Collection where Element: Equatable {
public func split(separator: Element, maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true) ->[SubSequence]
}
这个函数和 String 从 NSString 继承来的 components(separatedBy:) 很类似,不过还多加了一个决定是否要丢弃空值的选项.
4.StringProtocol
Substring 和 String 的接口几乎完全一样。这是通过一个叫做 StringProtocol 的通用协议来达到的,String 和 Substring 都遵守这个协议。因为几乎所有的字符串 API 都被定义在StringProtocol 上,对于 Substring,你完全可以假装将它看作就是一个 String,并完成各项操作,直到需要传递到下一个系统,或者需要保存起来的时候再初始化为Sting.
但是也因为如此,substring会持有原字符串,需要防止内存泄漏,或者不必要的开销.
如果你想要扩展 String 为其添加新的功能,将这个扩展放在 StringProtocol 会是一个好主意,这可以保持 String 和 Substring API 的统一性.
但是需要注意的是,标准库警告不要声明任意新的遵守 StringProtocol 协议的类型。只有标准库中的String 和Substring 类型是有效的适配类型。
5.CharacterSet
Foundation有一个CharacterSet,但是在swift面前它名不副实,其实是 UnicodeScalar的Set,就是一个表示
一系列 Unicode 标量的数据结构体,完全和 Character 类型不兼容.
6.CustomStringConvertible 和CustomDebugStringConvertible
类似OC的description,可以给任意类型实现这两个协议,用来格式化print输出的内容.
如果没有实现 CustomDebugStringConvertible,String(reĪecting:) 会退回使用CustomStringConvertible。所以如果你的类型很简单,通常没必要实现CustomDebugStringConvertible。不过如果你的自定义类型是一个容器,那么遵循CustomDebugStringConvertible 以打印其所含元素的调试描述信息会更考究一些.
extension AlertView:CustomStringConvertible, CustomDebugStringConvertible{
var debugDescription: String {
return ""
}
var description: String{
return ""
}
}