如果我在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?
}
}
在我的用例中,我用nils填充了数组:
let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]
switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
// process last component with 5
default:
break
}
还可以查看Erica Sadun / Mike Ash的safe:标签的下标扩展:http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/
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线程。
我知道这是个老问题了。我现在用的是Swift5.1, OP是swift1还是swift2 ?
今天我需要这样的东西,但是我不想仅仅为一个地方添加一个完整的扩展,而想要一些更实用的扩展(更线程安全?)我也不需要防止负标,只需要保护那些可能超过数组末尾的负标:
let fruit = ["Apple", "Banana", "Coconut"]
let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil
对于那些争论具有nil的序列的人,对于空集合返回nil的第一个和最后一个属性该怎么办?
我喜欢这个,因为我可以抓住现有的东西,用它来得到我想要的结果。我也知道dropFirst(n)不是一个完整的集合副本,只是一个切片。然后已经存在的第一个行为接管了我。
不知道为什么没有人,已经提出了一个扩展,也有一个setter自动增长数组
extension Array where Element: ExpressibleByNilLiteral {
public subscript(safe index: Int) -> Element? {
get {
guard index >= 0, index < endIndex else {
return nil
}
return self[index]
}
set(newValue) {
if index >= endIndex {
self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
}
self[index] = newValue ?? nil
}
}
}
使用很容易,工作在Swift 5.1
var arr:[String?] = ["A","B","C"]
print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]
arr[safe:10] = "Z"
print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]
注意:你应该理解在swift中增长数组时的性能成本(在时间/空间上)-但对于小问题,有时你只需要让swift停止自己的swift
我认为这不是一个好主意。似乎更可取的做法是构建稳定的代码,不导致试图应用越界索引。
请考虑让这样的错误通过返回nil无声地失败(正如上面的代码所建议的那样),很容易产生更复杂、更难以处理的错误。
你可以用类似的方式重写,用你自己的方式写下标。唯一的缺点是现有的代码将不兼容。我认为找到一个钩子来覆盖通用的x[I](也没有C中的文本预处理器)将是具有挑战性的。
我能想到的最接近的就是
// compile error:
if theIndex < str.count && let existing = str[theIndex]
编辑:这实际上是可行的。一行程序! !
func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
return idx < array.count ? array[idx] : nil
}
if let x: AnyObject = ifInBounds(swiftarray, 3) {
println(x)
}
else {
println("Out of bounds")
}