Swift 5中的String
采用了UTF-8编码。而NSString
是UTF-16编码的。NSString
到String
的转换是lazy
的,这句话充满了刀光剑影。
所谓lazy
是Swift
中最常见的用法,简单的描述就是,当在需要复制的时候,不进行复制,而仅标记,然后如果后面的操作是读操作,就一直读,直到出现了写操作,才会真正将内容分离写入。这么做的好处,是性能比较好,如果有幸最终也没有写入操作,那么就完全省去了写入操作和额外的内存占用。
不过由于String
和NSString
的编码不同,这种lazy
导致了一个严重的问题。就是如果你从某个框架获得了一个String
,你其实是不知道它是原生的String
,还是桥
过来的NSString
。比如你读取了一个String.Index
,等你要用的时候,它可能已经失效了。
举一个简单的例子:
import Foundation
let ns:NSString = "ab两只老虎,两只老虎,跑得快,跑得快。"
var s = ns as String
let aIndex = s.firstIndex(of: "只")!
print(s[aIndex]) // 只
s += ""
print(s[aIndex]) // \270
为了解决上面的问题,Swift有两项硬性规定。
- 对于
String.Index
,索引只对于它自身的String
。使用非自身字符串的索引,可能导致未知的问题。 String
只要有任何改变,String.Index
都应该重新获取。
解决办法
由于String.Index
非常容易失效,且不能直接使用。因此,在一个字符串使用另一个字符串的索引是需要转换才能使用。但是,这种转换,Swift本身是没有直接提供的。需要自己算一下。
import Foundation
extension String {
func sameIndex(_ index:String.Index, of str:String) -> String.Index? {
let offSet = self.distance(from: self.startIndex, to: index)
return str.index(str.startIndex, offsetBy: offSet, limitedBy: str.endIndex)
}
}
let ns:NSString = "ab两只老虎,两只老虎,跑得快,跑得快。"
var s = ns as String
let aIndex = s.firstIndex(of: "只")!
print(s[aIndex]) // 只
let s1 = s + ""
let i1 = s.sameIndex(aIndex, of: s1)!
print(s1[i1]) // 只
于此类似,Range<String.Index>
也有同样的问题。更扩大一步说,只要是支持Collection
类型的,都有这个问题。