如果我在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/

其他回答

我知道这是个老问题了。我现在用的是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)不是一个完整的集合副本,只是一个切片。然后已经存在的第一个行为接管了我。

我认为这不是一个好主意。似乎更可取的做法是构建稳定的代码,不导致试图应用越界索引。

请考虑让这样的错误通过返回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")
}

你可以试试

if index >= 0 && index < array.count {
    print(array[index]) 
}

说实话,我也遇到过这个问题。从性能的角度来看,Swift数组应该能够抛出。 让x = try a[y] 这很好,也可以理解。

如果您确实需要这种行为,那么您需要的是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) ->元素?