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

当前回答

因为数组可以存储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
    }
}

其他回答

不知道为什么没有人,已经提出了一个扩展,也有一个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

2022

无限索引访问和安全idx访问(如果没有这样的idex,则返回nil):

public extension Collection {
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }

    subscript (infinityIdx idx: Index) -> Element where Index == Int {
        return self[ abs(idx) % self.count ]
    }
}

但是要小心,如果数组/集合为空,它将抛出异常

使用

(0...10)[safe: 11] // nil

(0...10)[infinityIdx: 11] // 0
(0...10)[infinityIdx: 12] // 1
(0...10)[infinityIdx: 21] // 0
(0...10)[infinityIdx: 22] // 1

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线程。

你可以试试

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

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