如果我在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语句来检查并确保索引小于str.count。
我尝试添加我自己的下标()实现,但我不确定如何将调用传递给原始实现,或者访问项目(基于索引)而不使用下标符号:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Swift列表中的“常见拒绝更改”包含了更改数组下标访问以返回可选而不是崩溃:
使数组<T>下标访问返回T?或T !而不是T:当前数组行为是有意的,因为它准确地反映了越界数组访问是一个逻辑错误的事实。改变当前行为将使Array访问速度减慢到不可接受的程度。这个话题之前已经提过很多次了,但不太可能被接受。
https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types
因此,基本下标访问不会更改为返回可选对象。
然而,Swift团队/社区似乎愿意为数组添加一个新的可选返回访问模式,无论是通过函数还是下标。
这已经在Swift Evolution论坛上提出并讨论过了:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
值得注意的是,克里斯·拉特纳给这个想法打了一个“+1”:
同意,最常被建议的拼写是:yourArray[safe: idx],这对我来说似乎很棒。加上这个,我是+1。
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13
因此,在Swift的未来版本中,这可能是开箱即用的。我鼓励任何想要它的人加入Swift Evolution线程。
Swift列表中的“常见拒绝更改”包含了更改数组下标访问以返回可选而不是崩溃:
使数组<T>下标访问返回T?或T !而不是T:当前数组行为是有意的,因为它准确地反映了越界数组访问是一个逻辑错误的事实。改变当前行为将使Array访问速度减慢到不可接受的程度。这个话题之前已经提过很多次了,但不太可能被接受。
https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types
因此,基本下标访问不会更改为返回可选对象。
然而,Swift团队/社区似乎愿意为数组添加一个新的可选返回访问模式,无论是通过函数还是下标。
这已经在Swift Evolution论坛上提出并讨论过了:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
值得注意的是,克里斯·拉特纳给这个想法打了一个“+1”:
同意,最常被建议的拼写是:yourArray[safe: idx],这对我来说似乎很棒。加上这个,我是+1。
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13
因此,在Swift的未来版本中,这可能是开箱即用的。我鼓励任何想要它的人加入Swift Evolution线程。
如果您确实需要这种行为,那么您需要的是Dictionary而不是Array。字典在访问缺少的键时返回nil,这是有意义的,因为要知道一个键是否存在于字典中要困难得多,因为这些键可以是任何东西,而在数组中,键必须在一个范围内:0才能计数。在这个范围内迭代是非常常见的,在这个范围内,您可以绝对确定在循环的每次迭代中都有一个真实的值。
我认为它不能这样工作的原因是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实现,因为您将破坏所有期望从数组中获得非可选值的代码。
相反,您可以继承Array的子类,并重写下标以返回可选对象。或者,更实际地说,您可以使用非下标方法来扩展Array。
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
}
Swift 3更新
func get(index: Int) ->T?需要func get(index: Int) ->元素?