用代码示例解释 Swift 中的 Ranges
Swift 中的范围(Ranges)允许我们选择字符串、集合和其他类型的部分内容。它们是我们从 Objective-C 中熟悉的 NSRange 的 Swift 变体,尽管在使用上有所不同,我将在本篇博文中解释清楚。
通过使用范围运算符,范围使我们能够编写优雅的 Swift 代码。你第一次使用范围可能是因为你需要从字符串中选择一段字符,但是实际上你可以做的远不止这些!
Swift 中的范围类型
Swift 中有多种类型的范围可供我们使用。使用范围运算符是与它们交互的最简单方法。让我们来看看 Swift 中可用的不同类型。
闭合范围运算符 a...b
let range: ClosedRange = 0...10
print(range.first!) // 0
print(range.last!) // 10
使用 a...b
的闭合范围运算符定义了一个包含 a 和 b 的范围,其中 a 不能大于 b。
闭合范围运算符在你想要使用所有值的情况下非常有用。例如,如果你想迭代遍历一个集合的所有元素:
let names = ["Antoine", "Maaike", "Jaap"]
for index in 0...2 {
print("Name \(index) is \(names[index])")
}
// 输出:
// Name 0 is Antoine
// Name 1 is Maaike
// Name 2 is Jaap
不过,要从集合中选择元素,需要使用 CountableClosedRange
类型:
let names = ["Antoine", "Maaike", "Jaap"]
let range: CountableClosedRange = 0...2
print(names[range]) // ["Antoine", "Maaike", "Jaap"]
当然,Swift 聪明到足够自动检测到可数范围的类型。因此,你可以这样写上面的代码:
let names = ["Antoine", "Maaike", "Jaap"]
print(names[0...2]) // ["Antoine", "Maaike", "Jaap"]
半开范围运算符 a..<b
swiftCopy code
let range: Range = 0..<10
print(range.first!) // 0
print(range.last!) // 9
半开范围定义了从 a 到 b 的范围,但不包括 b。它被称为半开,因为它包含第一个值但不包含最后一个值。与闭合范围一样,a 的值不能大于 b。
半开范围运算符可用于迭代遍历从零开始的列表,如 Swift 中的数组和集合,其中你希望迭代遍历列表的长度但不包括最后一个元素。这与前面的代码示例相同,但现在我们可以利用
count
属性:
let names = ["Antoine", "Maaike", "Jaap"]
print(names[0..<names.count]) // ["Antoine", "Maaike", "Jaap"]
如果我们使用闭合运算符做同样的操作,将会遇到以下错误:
Fatal error: Array index is out of range
单侧范围运算符 a...
单侧范围运算符只定义了范围的一侧界限,例如 a...
或 ...b
。单侧范围尽可能地在一个方向上延伸,例如,从数组的开头到索引 2,包括数组的所有元素:
let names = ["Antoine", "Maaike", "Jaap"]
print(names[...2]) // ["Antoine", "Maaike", "Jaap"]
或者从索引 1 开始到数组的末尾的所有元素:
let names = ["Antoine", "Maaike", "Jaap"]
print(names[1...]) // ["Maaike", "Jaap"]
单侧范围可以用于迭代,但只有在与起始值 a...
一起使用时才能这样做。否则,循环的起始位置不明确。使用单侧范围进行迭代需要手动检查循环何时应该结束,否则它将无限继续下去。
let names = ["Antoine", "Maaike", "Jaap"]
let neededNames = 2
var collectedNames: [String] = []
for index in 0... {
guard collectedNames.count != neededNames else { break }
collectedNames.append(names[index])
}
print(collectedNames) // ["Antoine", "Maaike"]
在 Swift 中将范围转换为 NSRange
迟早你会遇到一个问题,即想要将 Range
转换为 NSRange
类型。例如,如果你正在使用 NSAttributedString
,并想要将属性应用于特定的范围。在下面的示例中,我们想要将橙色应用于标题中的“Swift”:
let title = "A Swift Blog"
let range = title.range(of: "Swift")
let attributedString = NSMutableAttributedString(string: title)
attributedString.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.orange], range: range) // 无法将类型为 'Range<String.Index>?' 的值转换为预期的参数类型 'NSRange'(即 '_NSRange')
由于 Range
无法转换为 NSRange
,我们遇到了以下错误:
无法将类型 'Range?' 的值转换为预期的参数 类型 'NSRange'(即 '_NSRange')
我们可以通过使用 NSRange
提供的方便的初始化器来解决这个问题,该初始化器接受一个 Swift Range
:
let convertedRange = NSRange(range, in: title)
最终的代码如下所示:
let title = "A Swift Blog"
let range = title.range(of: "Swift")!
let convertedRange = NSRange(range, in: title)
let attributedString = NSMutableAttributedString(string: title)
attributedString.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.orange], range: convertedRange)
print(attributedString)
// A {
// }Swift{
// NSColor = "UIExtendedSRGBColorSpace 1 0.5 0 1";
// } Blog{
// }
范围和字符串
字符串和范围有一些特殊之处。你可能知道,String
是字符的集合。然而,并非每个字符都具有相同的大小。我们可以通过使用包含表情符号的 NSRange
和 NSString
来演示这一点:
let emojiText: NSString = "🚀launcher"
print(emojiText.substring(with: NSRange(location: 0, length: 7)))
// 期望输出:🚀launch
// 实际输出:🚀launc
// 缺少了 'h'
如你所见,火箭表情符号的长度超过一个字符。因此,我们的子字符串没有返回预期的结果,缺少了 'h'。
使用 String 索引处理
解决这个问题的方法是使用 Range<String.Index>
而不是 Range<Int>
。String.Index
考虑了字符的实际大小。我们只能使用半开范围(Range
)的形式,因为这是 String
下标所要求的。
let emojiText = "🚀launcher"
let endIndex = emojiText.index(emojiText.startIndex, offsetBy: 7)
let range: Range<String.Index> = emojiText.startIndex..<endIndex
print(emojiText[range]) // 🚀launch