如果我在 Swift 中有一个数组,并尝试访问一个越界的索引,则会出现一个不足为奇的运行时错误:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
然而,我本以为Swift带来的所有可选链接和安全性,做以下事情是微不足道的:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
而不是:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
但事实并非如此——我必须使用ol' if
语句来检查并确保索引小于< code>str.count。
我尝试添加自己的< code>subscript()实现,但是我不确定如何将调用传递给原始实现,或者在不使用下标符号的情况下访问项目(基于索引):
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Alex的回答对这个问题有很好的建议和解决方案,但是,我碰巧发现了实现此功能的更好方法:
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
let array = [1, 2, 3]
for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}
如果你真的想要这种行为,它闻起来就像你想要一个字典而不是数组。字典在访问丢失的键时返回nil
,这是有道理的,因为很难知道字典中是否存在键,因为这些键可以是任何东西,在数组中,键必须在以下范围内:0
to计
。在这个范围内迭代是非常常见的,在这个范围内,您可以绝对确定在循环的每次迭代中都有一个实值。
我认为它不能以这种方式工作的原因是Swift开发人员做出的设计选择。举个例子:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"
如果您已经知道索引存在,就像您在使用数组的大多数情况下所做的那样,这段代码很棒。但是,如果访问下标可能会返回nil
,那么您已将Array
的下标
方法的返回类型更改为可选。这将您的代码更改为:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
// ^ Added
这意味着每次遍历数组或使用已知索引执行任何其他操作时,您都需要解包装可选项,只是因为您很少访问越界索引。Swift设计者选择了较少的可选项解包装,以访问越界索引时的运行时异常为代价。崩溃比您在某处的数据中没有预料到的nil
引起的逻辑错误更可取。
我同意他们的看法。因此,您不会更改默认的 Array
实现,因为您会破坏所有需要数组中非可选值的代码。
相反,您可以子类<code>Array</code>,并重写<code>下标</code〕以返回可选。或者,更实际地,您可以使用非下标方法扩展<code>Array</code>。
extension Array {
// Safely lookup an index that might be out of bounds,
// returning nil if it does not exist
func get(index: Int) -> T? {
if 0 <= index && index < count {
return self[index]
} else {
return nil
}
}
}
var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
print("I ate a \( fruit )")
// I ate a Banana
}
if let fruit = fruits.get(3) {
print("I ate a \( fruit )")
// never runs, get returned nil
}
func-get(索引:Int)-
根据Nikita Kukushkin的回答,有时您需要安全地将数组索引赋值并从中读取,即
myArray[safe: badIndex] = newValue
因此,这是对Nikita答案(Swift 3.2)的更新,它还允许通过添加安全:参数名称安全地写入可变数组索引。
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
extension MutableCollection {
subscript(safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set(newValue) {
if let newValue = newValue, indices.contains(index) {
self[index] = newValue
}
}
}
}