你需要了解的 Swift 4 新东西之 Substring

Swift 4 正式版已经随着 iOS 11 和 Xcode 9 的发布而发布了。

这是一个值得受好评的版本,很重要的一点是,它改变了 Swift 被人吐槽最多的一个问题:每个版本因为不兼容的问题,都要重写。Swift 4 有着 Swift 3 语法兼容模式,这意味着你不用改一行代码,就可以在 Xcode 编译通过。这对大型 iOS 项目而已非常重要:我们可以在未来的一年从一个 Target、一个 Framework 开始的形式慢慢地迁移至 Swift 4。

由于我比较急,一下子把自己的项目(奇点微博客户端)就迁移至 Swift 4,并已上架 App Store。在这个过程中发现了一些新东西。其中就有 Substring。

String 的主要变化

Swift 4 的 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 了:

String 和 Substring

当我们用一些 Collection 的方式得到 String 的 Slice(一部分)的时候,这时候创建的都是 Substring,Substring 与 String 共享一个 Storage,这意味我们在操作 String 的一部分的时候,是不需要频繁的去创建内存的,这使得 Swift 4 的 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 4 的这个设计,还是提供了不少灵活性的。

常规的使用场景

可能大家看到这,会觉得 Swift 4 引入了复杂度,用 String 就用 String 好了嘛,非要搞一个 Substring 出来。但事实上,在大多数项目里,我们不会遇到要用 String 还是用 Substring 这个「选择」问题。

因为 Swift 是强类型的语言,这意味着如果你的函数参数写着「name: String」,这是不接受 Substring 的,你一定要显式的转成 String,而转换成 String 的过程就意味着进行 Copy,也就不规避了共享 Storage 的问题,不会存在内存占用的问题。

只有当你特别需要去优化时,才需要好好去设计和思索一下要用 Substring 或是 String 来做操作。

一个实用的 Extension

这是我用在奇点里面的一个 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 或 Substring 的问题吧。

欢迎使用图拉鼎和他的团队开发的作品

One Switch - 多功能开关工具

常驻 macOS 菜单栏的开关工具,可以快速开关 AirPods、睡眠模式、切换黑暗模式等。

3 Comments

swift4.0的 ABI 还是没稳定吧

tualatrix 回复 @東引甌越

嗯,要到 Swift 5.0 才稳定。

Tao

欢迎分享到掘金 https://juejin.im 上~

Leave a Comment