WWDC 已经结束了(我觉得是自 2014 年来最好的一场 WWDC),同时 Xcode 9 beta 版也发布了,很多开发者已经开始把玩 Swift 4 ,今年的新版本真心不错,这是一个改进版本而不是重构版本(像 Swift 2 和 3),因此大多数代码升级起来会更容易。
其中一个改进是 String 的 API,在 Swift 4 中更易用,也更强大。在过去的 Swift 版本中,String API 经常被提出为一个例子用来说明正确性,以繁琐的方式处理字符和子串。
本周,我们来看看在 Swift 4 中如何使用 String,以及如何在各种情况下利用新的改进的 API。
多行字符串
有时我们的应用或脚本中有很长的静态字符串会跨越多行。在 Swift 4之前,我们只能在字符串之间插入 \n
来换行,appendOnNewLine()
通过给 String 添加一个 extension
方法,多次print()
来添加换行符用来大段文本输出。
译者注:这里的脚本指的是命令行程序
比如,下面是 TestDrive
的 printHelp()
方法(用于打印脚本的使用说明)在 Swift 3 看起来如下:
func printHelp() {
print(" Test Drive")
print("--------------")
print("Quickly try out any Swift pod or framework in a playground.")
print("\nUsage:")
print("- Simply pass a list of pod names or URLs that you want to test drive.")
print("- You can also specify a platform (iOS, macOS or tvOS) using the '-p' option")
print("- To use a specific version or branch, use the '-v' argument (or '-m' for master)")
print("\nExamples:")
print("- testdrive Unbox Wrap Files")
print("- testdrive https://github.com/johnsundell/unbox.git Wrap Files")
print("- testdrive Unbox -p tvOS")
print("- testdrive Unbox -v 2.3.0")
print("- testdrive Unbox -v swift3")
}
Swift 4 中处理多行文本字符串:
func printHelp() {
print(
"""
Test Drive
--------------
Quickly try out any Swift pod or framework in a playground.
Usage:
- Simply pass a list of pod names or URLs that you want to test drive.
- You can also specify a platform (iOS, macOS or tvOS) using the '-p' option
- To use a specific version or branch, use the '-v' argument (or '-m' for master)
Examples:
- testdrive Unbox Wrap Files
- testdrive https://github.com/johnsundell/unbox.git Wrap Files
- testdrive Unbox -p tvOS
- testdrive Unbox -v 2.3.0
- testdrive Unbox -v swift3
"""
)
}
如上所示,当使用多行文本字符串时,代码会表示得更加清楚和整洁,我们不需要再添加很多 \n
换行符,而是简单地在字符串中添加真正的换行符,使得在运行脚本之前,可以很容易地看到输出后的样子。
在缩进方面,多行文本字符串使用底部的 """
来确定字符串的基础缩进,所以跟这些引号对齐的所有内容都不会在字符串中进行额外的真实缩进。
字符串是集合(再次)
在 Swift 1 中,String 遵循了 CollectionType
(Swift 3+ 中的 Collection
)协议,这意味着您可以对它们执行各种收集操作(比如 forEach()
,filter()
等)。 您仍然可以在 Swift 2 & 3 中进行此操作,通过访问 characters
属性,但这很快会导致阅读代码更难。
Swift 4 中字符串再次成为了集合对象,这意味着你可以简单地将它们视为“字符集合”。这可能会很有用,比如从字符串中过滤出某些字符(让我们以叹号为例):
let filtered = string.filter { $0 != "!" }
新的 Substring 类型
Swift 4 引入了一种处理子字符串的新方法,使用了一个完全不同的类型 Substring 。现在大多数方法都会返回 Substring(如 split()
),并且还引入了一个新的 subscripting API,让你可以快速访问一个子字符串:
// 从一个置顶的
let substring = string[index...]
我们来看一个例子,我们截断用户输入的文本返回一个子字符串,将其限制在一定长度。在 Swift 3 中,你会写成这样:
extension String {
func truncated() -> String {
return String(characters.prefix(truncationLimit))
}
}
(编辑注释:我之前用的是一个更复杂的例子,感谢 Ole Begemann 指出这个更好的解决方案)
好消息是,由于 Swift 4 代码大多数兼容 Swift 3,所以上述代码同样可以在 Swift 4 下运行。但更好的消息是,由于 String 在 Swift 4 中是集合类型,我们可以直接简化上述代码为:
extension String {
func truncated() -> Substring {
return prefix(truncationLimit)
}
}
上面用 prefix()
方法返回最多为 n 个元素的子序列,同时包括边界检查(如果我们的集合(这里是字符串)包含少于 n 个元素的时候,以防我们不会遇到错误)。
你可以看到上面的 truncated()
方法的返回类型现在是新的 Substring 类型。虽然起初看起来很累赘因为字符串现是一个不同的类型,但它在内存可预测性方面给了我们很大的优势。
为了避免创建许多冗余拷贝,Swift 字符串使用 “copy on write” 方式只需要在需要时进行拷贝。这意味着子字符串通常与内存中的父字符串共享相同的底层缓冲区。但是在truncated()
这个例子中,我们不想在内存中保留整个未截断的字符串,只是为了能够使用截断的子字符串。
通过给我们一个 Substring 类型,而不是一个完整的 String,Swift 现在 “强制” 我们在需要时显式拷贝副本,让父字符串的内存可以被释放。我们可以简单地从截取的子字符串创建一个新的 String ,如下所示:
label.text = String(userInput.truncated())
结论
由于几乎所有的 Swift 应用和脚本都会处理字符串,所以很高兴能看到这些 API 的改进。我认为新的 API 是在正确性和易用性之间的一个很好的权衡,同时也要求程序员在 copy 时慎重选择,像 Substring 一样。
我没有在这篇文章中介绍的字符串 API 也有更多的进步,比如 Unicode 9 支持更简单的字符管理,并且能够轻松访问构成字符的底层 Unicode 代码。
我们将在即将发布的后续文章包括 WWDC 中发布的其他新的 Swift 4 API 、框架、工具中深入研究字符串处理和编码。敬请关注!