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

当前回答

extension Array {
    subscript (safe index: Index) -> Element? {
        0 <= index && index < count ? self[index] : nil
    }
}

O(1)的性能 类型安全 正确处理[MyType?)(返回MyType??,可以在两个级别上展开) 不会导致set的问题吗 简洁的代码

这是我为你做的一些测试:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?

其他回答

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

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

基于Nikita Kukushkin的回答,有时候你需要安全地赋值给数组下标,也需要从它们中读取。

myArray[safe: badIndex] = newValue

因此,这里是对Nikita的答案(Swift 3.2)的更新,通过添加safe:参数名,也允许安全写入可变数组索引。

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
            }
        }
    }
}

因为数组可以存储nil值,如果数组[index]调用越界,返回nil是没有意义的。 因为我们不知道用户想如何处理越界问题,所以使用自定义操作符没有意义。 相反,使用传统的控制流来展开对象并确保类型安全。

if let index = array.checkIndexForSafety(index:Int) let item = array[safeIndex: index] if let index = array.checkIndexForSafety(index:Int) array[safeIndex: safeIndex] = myObject

extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}

斯威夫特4

为那些更喜欢传统语法的人提供的扩展:

extension Array {
    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
extension Array {
    subscript (safe index: Index) -> Element? {
        0 <= index && index < count ? self[index] : nil
    }
}

O(1)的性能 类型安全 正确处理[MyType?)(返回MyType??,可以在两个级别上展开) 不会导致set的问题吗 简洁的代码

这是我为你做的一些测试:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?