Swift 3 字符串的进化

原文: Updating Strings for Swfit 3
作者: kharrison
译者: kemchenj

我去年写了一篇 Swift String Cheat Sheet 来帮助我记忆如何使用 Swift 标准库里的那些难用的 API, 在痛苦的版本迁移之后, Swift 3有了明显的改善, 这一部分得归功于新的 API 命名规范, 还有 Collections 集合, indicates 索引和 ranges 范围的一种新的运作方式.

这里有我关于 Swift 3的迁移工作的总结 Swift playground.

更好的 API 命名

标准库采用了新的 API 命名规范 API guidelines 之后, String 的属性和方法都有了很多改变. 因为大部分 API 命名的变化 Xcode 都会自动帮你修正, 所以我不会在这里把全都都列出来. 这里列出一些典型的改变让你能更好的了解这次变化:

初始化一个 String

标准库把 String 的初始化方法 init(count: repeatedValue:) 改成了 init(repeating: count:), repeatedValue 的类型也从 Character 字符换成了 String 字符串去获得更多灵活性:

// Swift 2
let h = String(count:3, repeatedValue:"0") // "000"
// Swift 3
let h = String(repeating:"01", count:3) // 010101

大小写转换

uppercaseStringlowercaseString 两个属性现在变成了函数, 重新命名为 uppercased()lowercased():

let mixedCase = "AbcDef"
// Swift 2
// let upper = mixedCase.uppercaseString // "ABCDEF"
// let lower = mixedCase.lowercaseString // "abcdef"
// Swift 3
let upper = mixedCase.uppercased() // "ABCDEF"
let lower = mixedCase.lowercased() // "abcdef"

接下来我还会讲到一些别的命名的变化

使用索引去访问集合

Swift 3 里对于 String 影响最大的一个变化就是 new model for collections and indices. 总结起来就是你不能直接访问 String 里的元素, 而必须使用索引去从集合里把元素取出来:

let country = "España"
country.characters // characters
country.unicodeScalars // Unicode scalar 21-bit codes
country.utf16 // UTF-16 encoding
country.utf8 // UTF-8 encoding

Swift 3里每个集合的 view 里的 startIndexendIndex 属性还是没变:

let hello = "hello"
let helloStartIndex = hello.characters.startIndex // 0

如果想要获取字符的集合, 你也可以使用 characters 属性:

(译者注: characters 能够自动帮助我们处理编码问题, 让我们获得人类理解的”字符集合”. Swift 的字符串 API 刚接触可能会觉得很难用, 但了解了背后的原理之后, 会发现它其实做了很多, 帮我们避开了很多坑, 了解方法之后也很容易使用)

let startIndex = hello.startIndex // 0
let endIndex = hello.endIndex // 5
hello[startIndex] // "h"

之前通过增减索引去访问字符串的方式改变了, successor(), predecessor()advancedBy(n) 的函数都去掉了.

// Swift 2
hello[hello.startIndex] // "h"
hello[hello.startIndex.successor()] // "e"
hello[hello.endIndex.predecessor()] // "o"
hello[hello.startIndex.advancedBy(2)] // "l"

现在在 Swift 3 里你会使用 index(after:), index(before:)index(_: offsetBy:) 去处理相同的情况:

// Swift 3
hello[hello.startIndex] // "h"
hello[hello.index(after: startIndex)] // "e"
hello[hello.index(before: endIndex)] // "o"
hello[hello.index(startIndex, offsetBy: 1)] // "e"
hello[hello.index(endIndex, offsetBy: -4)] // "e"

你也可以给 offset 加上限制, 避免错误的下标访问. 函数 index(_: offsetBy: limitedBy:) 会返回一个可选值, 下标越界的时候就会返回 nil:

if let someIndex = hello.index(startIndex,
offsetBy: 4, limitedBy: endIndex) {
hello[someIndex] // "o"
}

找到第一个符合条件的元素 <T> 的方式(在这种情况下, T 是一个字符串):

let matchedIndex = hello.characters.index(of: "l") // 2
let nomatchIndex = hello.characters.index(of: "A") // nil

最后, 获取两个索引之间距离的方法现在也被重新命名了:

// Swift 2
let distance = word1.startIndex.distanceTo(indexC)
// Swift 3
let distance = word1.distance(from: word1.startIndex, to: indexC)

使用 Ranges (范围)

Swift 3 里对 Ranges 进行了修改. 假设我有字符集合的一个起始索引值和一个终点索引值:

let fqdn = "useyourloaf.com"
let tldEndIndex = fqdn.endIndex
let tldStartIndex = fqdn.index(tldEndIndex, offsetBy: -3)

用起始和终点索引去初始化 Range 的方式:

let range = Range(uncheckedBounds: (lower: tldStartIndex, upper: tldEndIndex))
fqdn[range] // "com"

创建一个范围最简单的方法就是使用 ..<... 操作符:

let endOfDomain = fqdn.index(fqdn.endIndex, offsetBy: -4)
let rangeOfDomain = fqdn.startIndex ..< endOfDomain
fqdn[rangeOfDomain] // useyourloaf

查找和返回子字符串的范围:

if let rangeOfTLD = fqdn.range(of: "com") {
let tld = fqdn[rangeOfTLD] // "com"
}

Playground

你可以在这里找到一份完整的升级后的 API 变化的 playground 文件 Code Examples repository. 我也更新了之前我写的那篇文章 original post.

参考阅读