概述
Swift中的 String是一个 Collection,可以拥有和 Array 等 Collection 一样的操作方式,比如:
let text = "Hello world"
for char in text {
print(char)
}
text.prefix(5) // "Hello"
text.suffix(5) // "world"
text.dropFirst() // "H"
text.dropLast() // "d"
上面所有针对 text 的方法都是在 Array(或其他 Collection)上的有。但是这里有一点要注册,这些方法返回的,可不是 String,而是 Substring。
Substring 是什么?
String 和 Substring 都遵守了 StringProtocol 这个东西,很多方法也都是一样的,那么为何要有这个设计呢?主要一个原因是:性能。
看了这张图,你就知道什么是 Substring 了:
当我们用一些 Collection 的方式得到 String 的 Slice(一部分)的时候,这时候创建的都是 Substring,Substring 与 String 共享一个 Storage,这意味我们在操作 String 的一部分的时候,是不需要频繁的去创建内存的,这使得 Swift 的 String 相关的操作可以获取比较高的性能。
只有当你显式地将 Substring 转成 String 的时候,才会 Copy 一份 String 到新的内存空间来,这时新的 String 和之前的 String 就没有关系了。
那么作为开发者,我们是不是就可以高枕无忧,只需要用,不需要操心什么是 Substring 什么是 String 呢?当然不是。
使用 Substring 的注意点
由于 Substring 是共享 String 的 Storage 的,这意味着,假如在你从一个非常大的 String 里得到一个 Substring,即使在之后你只用 Substring 了,但内存依然是占用着整个 String 的大小。直到 Substring 被释放以后,整个 String 才会被释放。
理解了这个原理后,我们就可以在具体的场景去优化性能了。只要记住两个原则:1、创建 Substring 的性能快,因为共享 String 的 Storage;2、如果有 Substring 存在,则 String 的 Storage 不会释放。
可以说,Swift的这个设计,还是提供了不少灵活性的。
常规的使用场景
可能大家看到这,会觉得 Swift 引入了复杂度,用 String 就用 String 好了嘛,非要搞一个 Substring 出来。但事实上,在大多数项目里,我们不会遇到要用 String 还是用 Substring 这个「选择」问题。
因为 Swift 是强类型的语言,这意味着如果你的函数参数写着「name: String」,这是不接受 Substring 的,你一定要显式的转成 String,而转换成 String 的过程就意味着进行 Copy,也就规避了共享 Storage 的问题,不会存在内存占用的问题。
只有当你特别需要去优化时,才需要好好去设计和思索一下要用 Substring 或是 String 来做操作。
一个实用的 Extension
把 Substring 显式地转成 String。
extension String {
public func substring(from index: Int) -> String {
if self.characters.count > index {
let startIndex = self.index(self.startIndex, offsetBy: index)
let subString = self[startIndex..<self.endIndex]
return String(subString)
} else {
return self
}
}
}
比如我是这样用的:
let text = "@图拉鼎"
let name = text.substring(from: 1)
print(name) // "图拉鼎"
简单地举这个例子,就是想说平常使用中,也可以完全无视 Substring 这个东西,只需要把常用的操作写成 extension 即可。
String的增、删、替换
String的Index
swift中的字符串不能直接通过下标[]整数的形式访问
var line = "I want magic"
line[0]// 编译报错
indices 属性会创建一个包含全部索引的数组
for i in line.indices{
print(line[i])// OK的
}
除此之外,index(_:offsetBy:)获取指定位置的索引
var version = "10.11.12"
//对于一个确定的字符串,能直接获取到它的startIndex,endIndex
//endIndex 是最后一个字符之后的索引
var prefixIndex = version.startIndex
var suffixIndex = version.endIndex
//获取 第二个索引
var secondIndex = version.index(prefixIndex, offsetBy: 2)
//获取倒数第二个索引
var reversalSecondIndex = version.index(suffixIndex, offsetBy: -2)
var index = line.firstIndex(of: "0") ?? line.endIndex
有了String.Index字符串截取,通过下标[]运算访问
//substring(to:)已弃用
//从首位到索引位置的字符串
print(version.substring(to: secondIndex))//10
print(version.substring(to: reversalSecondIndex))//10.11.
//倒数到索引位置的字符串
//substring(from:)已弃用
print(version.substring(from: secondIndex))//.11.12
print(version.substring(from: reversalSecondIndex))// 12
// range 注意区间右边越界 suffixIndex 是最后一个字符之后的索引
print(version[secondIndex]) // .
print(version[prefixIndex..<suffixIndex]) //"10.11.12"
print(version[prefixIndex...suffixIndex]) //越界 异常
print(version[secondIndex...reversalSecondIndex])//.11.1
print(version[index]) // 0
print(version[...index])//10
print(version[index...])//0.11.12
替换
var time = "2021:1:1"
var newTime = time.replacingOccurrences(of: ":", with: "-")
print(newTime)//2021-1-1
//替换一段字符
var strat = time.index(time.startIndex, offsetBy: 4)
var end = time.index(time.startIndex, offsetBy: 5)
var newTime2 = time.replacingCharacters(in: strat..<end, with: "AAAA")
print(newTime2)//2021AAAA1:1
删除
/ 删除 单个字符
time.remove(at: time.index(time.startIndex, offsetBy: 4))
print(time)//20211:1
//删除一段字符
time.removeSubrange(time.range(of: "2021")!) //:1:1
time.removeSubrange(time.startIndex..<time.endIndex)// 空字符串
//等效写法
time.removeAll()
插入
//在指定索引 前面 插入字符
time.insert("A", at: strat)//A2021:1:1
//在指定索引 前面 插入多个字符
time.insert(contentsOf: "BBBB", at: strat)//BBBB2021:1:1
过滤trimmingCharacters(in:)会对字符串的开头和结尾过滤处理
var time = " 2021:1:1 "
//过滤前后空格
var nowhitespaces = time.trimmingCharacters(in: .whitespaces)
// 20BBBB21:1:1
//filter
time.filter { $0.isNumber }
// 202111
由于字符串遵守Sequence协议,所以Sequence系列方法也都适用