如果我在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&#39; if
语句用于检查并确保索引小于str.count
。
我尝试添加自己的subscript()
实现,但我不确定如何将调用传递给原始实现,或者不使用下标符号来访问项目(基于索引):
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
答案 0 :(得分:549)
Alex's answer对这个问题提出了很好的建议和解决方案,但是,我偶然发现了一种更好的实现此功能的方法:
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 Collection where Indices.Iterator.Element == Index {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
感谢Hamish提出the solution for Swift 3。
extension CollectionType {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Generator.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)
}
}
答案 1 :(得分:51)
如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组。字典在访问丢失的密钥时返回nil
,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中,密钥必须范围为:0
到count
。迭代这个范围是非常常见的,你可以绝对确定在循环的每次迭代中都有实际价值。
我认为它不能以这种方式工作的原因是Swift开发人员做出的设计选择。举个例子:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"
如果您已经知道索引存在,就像在大多数使用数组的情况下一样,这段代码很棒。但是,如果访问下标可能会返回nil
,那么已将Array
的{{1}}方法的返回类型更改为可选。这会将您的代码更改为:
subscript
这意味着每次迭代数组时都需要解包一个可选项,或者使用已知索引执行其他任何操作,因为很少有人可能会访问越界索引。 Swift设计者在访问越界索引时以牺牲运行时异常为代价,选择了较少的可选解包。并且崩溃比由您在某个地方没有预料到的var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
// ^ Added
引起的逻辑错误更可取。
我同意他们的观点。所以你不会改变默认的nil
实现,因为你会破坏所有需要数组中非可选值的代码。
相反,您可以继承Array
,并覆盖Array
以返回可选项。或者,更实际地,您可以使用执行此操作的非下标方法扩展subscript
。
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
}
func get(index: Int) ->
需要替换为T?
func get(index: Int) ->
答案 2 :(得分:12)
尽管已经有很多次回答了这个问题,但我想在Swift编程的流行方式中更加符合回答,用Crusty的话来说就是:“首先考虑protocol
“
•我们想做什么?
- 仅在安全时获取Array
给定索引的元素,否则nil
•此功能应该基于什么实施?
- Array
subscript
ing
•它从哪里获得此功能?
- struct Array
模块中Swift
的定义为
•没有更通用/抽象的东西?
- 它采用protocol CollectionType
来确保它
•没有更通用/抽象的东西?
- 它还采用protocol Indexable
......
•是的,听起来像我们能做的最好。我们可以扩展它以获得我们想要的功能吗?
- 但我们现在可以使用非常有限的类型(无Int
)和属性(无count
)!
•这就够了。 Swift的stdlib做得很好;)
extension Indexable {
public subscript(safe safeIndex: Index) -> _Element? {
return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
}
}
¹:不是真的,但它提出了这个想法
答案 3 :(得分:8)
如果让index = array.checkIndexForSafety(index:Int)
let item = array[safeIndex: index]
如果让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 :(得分:7)
基于Nikita Kukushkin的答案,有时您需要安全地分配数组索引以及从它们读取,即
myArray[safe: badIndex] = newValue
所以这里是对Nikita的答案(Swift 3.2)的更新,它还允许通过添加safe:参数名称安全地写入可变数组索引。
extension Collection {
/// Returns the element at the specified index iff 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
}
}
}
}
答案 5 :(得分:5)
extension Array {
subscript (safe index: Index) -> Element? {
return 0 <= index && index < count ? self[index] : nil
}
}
以下是我为你跑的一些测试:
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?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
答案 6 :(得分:5)
我意识到这是一个老问题。我现在正在使用Swift5.1,OP是用于Swift 1还是2?
我今天需要这样的东西,但是我不想只为一个地方添加完整的扩展,而是想要更多功能(更线程安全吗?)。我也不需要防范负索引,只需防范那些可能超出数组末尾的索引:
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的first
和last
属性?
我之所以喜欢它,是因为我可以抓住现有的东西,并用它来获得想要的结果。我也知道dropFirst(n)并不是一个完整的集合副本,而只是一个切片。然后first的已经存在的行为接管了我。
答案 7 :(得分:4)
我发现安全数组get,set,insert,remove非常有用。我更喜欢记录并忽略错误,因为很快就会难以管理。完整代码吼叫
/**
Safe array get, set, insert and delete.
All action that would cause an error are ignored.
*/
extension Array {
/**
Removes element at index.
Action that would cause an error are ignored.
*/
mutating func remove(safeAt index: Index) {
guard index >= 0 && index < count else {
print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
return
}
remove(at: index)
}
/**
Inserts element at index.
Action that would cause an error are ignored.
*/
mutating func insert(_ element: Element, safeAt index: Index) {
guard index >= 0 && index <= count else {
print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
return
}
insert(element, at: index)
}
/**
Safe get set subscript.
Action that would cause an error are ignored.
*/
subscript (safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set {
remove(safeAt: index)
if let element = newValue {
insert(element, safeAt: index)
}
}
}
}
测试
import XCTest
class SafeArrayTest: XCTestCase {
func testRemove_Successful() {
var array = [1, 2, 3]
array.remove(safeAt: 1)
XCTAssert(array == [1, 3])
}
func testRemove_Failure() {
var array = [1, 2, 3]
array.remove(safeAt: 3)
XCTAssert(array == [1, 2, 3])
}
func testInsert_Successful() {
var array = [1, 2, 3]
array.insert(4, safeAt: 1)
XCTAssert(array == [1, 4, 2, 3])
}
func testInsert_Successful_AtEnd() {
var array = [1, 2, 3]
array.insert(4, safeAt: 3)
XCTAssert(array == [1, 2, 3, 4])
}
func testInsert_Failure() {
var array = [1, 2, 3]
array.insert(4, safeAt: 5)
XCTAssert(array == [1, 2, 3])
}
func testGet_Successful() {
var array = [1, 2, 3]
let element = array[safe: 1]
XCTAssert(element == 2)
}
func testGet_Failure() {
var array = [1, 2, 3]
let element = array[safe: 4]
XCTAssert(element == nil)
}
func testSet_Successful() {
var array = [1, 2, 3]
array[safe: 1] = 4
XCTAssert(array == [1, 4, 3])
}
func testSet_Successful_AtEnd() {
var array = [1, 2, 3]
array[safe: 3] = 4
XCTAssert(array == [1, 2, 3, 4])
}
func testSet_Failure() {
var array = [1, 2, 3]
array[safe: 4] = 4
XCTAssert(array == [1, 2, 3])
}
}
答案 8 :(得分:1)
chainWebpack: config => {
const files = fs.readdirSync('./src/assets/themes/');
files.forEach(file => {
config.module.rule('scss').oneOf('vue')
.use('sass-vars-loader')
.loader('@epegzz/sass-vars-loader')
.options({
syntax: 'scss',
files: [
path.resolve(__dirname, `src/assets/themes/${file}`)
]
})
});
}
使用上面提到的扩展名,如果索引超出范围,则返回nil。
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}
结果-无
答案 9 :(得分:1)
不确定为什么没有人提供一个扩展程序,该扩展程序还具有一个自动增长数组的设置器
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本身就可以了。
答案 10 :(得分:1)
要传播操作失败的原因,错误比可选错误要好。下标不能引发错误,因此必须是一种方法。
public extension Collection {
/// - Returns: same as subscript, if index is in bounds
/// - Throws: CollectionIndexingError
func element(at index: Index) throws -> Element {
guard indices.contains(index)
else { throw CollectionIndexingError() }
return self[index]
}
}
/// Thrown when `element(at:)` is called with an invalid index.
public struct CollectionIndexingError: Error { }
XCTAssertThrowsError( try ["?", "?"].element(at: 2) )
let optionals = [1, 2, nil]
XCTAssertEqual(try optionals.element(at: 0), 1)
XCTAssertThrowsError( try optionals.element(at: optionals.endIndex) )
{ XCTAssert($0 is CollectionIndexingError) }
答案 11 :(得分:1)
Swift列表中的“常见拒绝更改”包含对 changing 数组下标访问权限的提及,以返回可选而不是崩溃:
使
Array<T>
下标访问返回T?
或T!
而不是T
:当前数组的行为是intentional,因为它准确地反映出边界数组访问是逻辑错误。更改当前行为会使Array
的访问速度减慢到无法接受的程度。该主题之前multiple次出现过,但极不可能被接受。
因此基本下标访问权限将不会更改以返回可选内容。
但是,Swift团队/社区似乎确实愿意向添加通过函数或下标向Arrays添加新的可选返回访问模式。
这已在Swift Evolution论坛上提出并讨论过:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
值得注意的是,克里斯·拉特纳(Chris Lattner)给这个想法打了“ +1”:
同意,为此,最常建议的拼写是:
yourArray[safe: idx]
,对我来说似乎很棒。我非常喜欢+1。https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13
因此在某些将来的Swift版本中,这可能是开箱即用的。我鼓励任何希望它为该Swift Evolution线程做出贡献的人。
答案 12 :(得分:1)
我在用例中用nil
填充了数组:
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/
答案 13 :(得分:0)
我对数组做了简单的扩展
extension Array where Iterator.Element : AnyObject {
func iof (_ i : Int ) -> Iterator.Element? {
if self.count > i {
return self[i] as Iterator.Element
}
else {
return nil
}
}
}
它按设计要求完美运行
示例
if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode,
答案 14 :(得分:0)
我认为这不是一个好主意。建立不会导致尝试应用越界索引的可靠代码似乎更可取。
请考虑通过返回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")
}
答案 15 :(得分:0)
您可以尝试
if index >= 0 && index < array.count {
print(array[index])
}
答案 16 :(得分:0)
RandomAccessCollection
上的扩展意味着这也适用于单个实现的 ArraySlice
。我们使用 startIndex
和 endIndex
作为数组切片使用来自底层父 Array
的索引。
public extension RandomAccessCollection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
/// - complexity: O(1)
subscript (safe index: Index) -> Element? {
guard index >= startIndex, index < endIndex else {
return nil
}
return self[index]
}
}
答案 17 :(得分:-1)
迅速5 使用情况
extension WKNavigationType {
var name : String {
get {
let names = ["linkAct","formSubm","backForw","reload","formRelo"]
return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
}
}
}
最终还是,但确实希望总体上喜欢
[<collection>][<index>] ?? <default>
但是由于集合是上下文相关的,所以我认为它是正确的。
答案 18 :(得分:-1)
当您只需要从数组中获取值并且您不介意对性能造成小的损失(即,如果您的集合不大)时,可以使用基于字典的替代方法不涉及(就我而言,太通用了)集合扩展:
// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;