如果我在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
.
我尝试添加自己的subscript()
实现,但我不知道如何将调用传递给原始实现,或者不使用下标符号来访问项目(基于索引):
extension Array { subscript(var index: Int) -> AnyObject? { if index >= self.count { NSLog("Womp!") return nil } return ... // What? } }
Nikita Kukus.. 600
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 } }
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 提出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) } }
我认为这绝对值得关注 - 干得好.我喜欢包含的`safe:`参数名称以确保区别. (38认同)
从Swift 2(Xcode 7)开始,这需要一点调整:`return self.indices~ = index?self [index]:nil;` (11认同)
关于夫特3版本:可能的角的病例仅-提示,但提示仍然:存在这样的情况,其中"安全"的下标以上版本不是安全的(而夫特2版本是):用于`Collection`类型"指数"不是连续的.例如,对于`Set`实例,如果我们要通过索引(`SetIndex
嘿,我已经更新了Swift 3的答案.我会坚持使用Swift 2一段时间,所以如果有任何问题,请随意指出. (4认同)
原因是主观的.我把我的实现读作"数组的索引包括索引".它简洁,对我来说,似乎比通过比较进行的边界检查更简单.我也喜欢玩新的API.您可以自由地用您的实现替换我的实现,但是您可能还想添加对负索引的检查以完全"安全". (3认同)
警告!用这种方法检查数组可能非常昂贵。“包含”方法将遍历所有索引,从而使其成为O(n)。更好的方法是使用索引和计数来检查界限。 (3认同)
@JonColverson是的,即使对于极端情况也应该是安全的,但是注意到我们放松了`O(1)`随机索引访问(我们做了,但是,对于上面的'contains`Swift 2版本),这应该是'但是,除非在具有大型阵列的某些HPC应用程序中工作,否则这是一个问题.对于另一个替代方案(类似于"O(n)"),它对应于上面Swift 2版本的更直接的Swift 2-> 3翻译,请参阅[此问答中的答案](http://stackoverflow.com/ a/40331858/4573247)(使用`contains`也允许"短路",就像你明确的`while`循环解决方案一样). (2认同)
Alex Wayne.. 56
如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组.字典nil
在访问丢失的密钥时返回,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中密钥必须在以下范围内:0
to count
.迭代这个范围是非常常见的,你可以绝对肯定在循环的每次迭代中都有一个真正的值.
我认为它不能以这种方式工作的原因是Swift开发人员做出的设计选择.举个例子:
var fruits: [String] = ["Apple", "Banana", "Coconut"] var str: String = "I ate a \( fruits[0] )"
如果您已经知道索引存在,就像在大多数使用数组的情况下一样,这段代码很棒.但是,如果访问标可能可能返回nil
,那么你已经改变了返回类型的Array
的subscript
方法是可选的.这会将您的代码更改为:
var fruits: [String] = ["Apple", "Banana", "Coconut"] var str: String = "I ate a \( fruits[0]! )" // ^ Added
这意味着每次迭代数组时都需要解包一个可选项,或者使用已知索引执行任何其他操作,因为很少有人可以访问超出范围的索引.Swift设计者在访问越界索引时以牺牲运行时异常为代价,选择了较少的可选解包.崩溃比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) ->
Element?
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 } }
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 提出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) } }
虽然已经有很多次回答了这个问题,但我想在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 } }
¹:不是真的,但它提出了这个想法
基于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 } } } }
因为数组可能存储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 } }
extension Array { subscript (safe index: Index) -> Element? { return 0 <= index && index < count ? self[index] : nil } }
O(1)表现
类型安全
正确处理[MyType?]的Optionals(返回MyType ??,可以在两个级别解包)
不会导致集合出现问题
简洁的代码
以下是我为你跑的一些测试:
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?
如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组.字典nil
在访问丢失的密钥时返回,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中密钥必须在以下范围内:0
to count
.迭代这个范围是非常常见的,你可以绝对肯定在循环的每次迭代中都有一个真正的值.
我认为它不能以这种方式工作的原因是Swift开发人员做出的设计选择.举个例子:
var fruits: [String] = ["Apple", "Banana", "Coconut"] var str: String = "I ate a \( fruits[0] )"
如果您已经知道索引存在,就像在大多数使用数组的情况下一样,这段代码很棒.但是,如果访问标可能可能返回nil
,那么你已经改变了返回类型的Array
的subscript
方法是可选的.这会将您的代码更改为:
var fruits: [String] = ["Apple", "Banana", "Coconut"] var str: String = "I ate a \( fruits[0]! )" // ^ Added
这意味着每次迭代数组时都需要解包一个可选项,或者使用已知索引执行任何其他操作,因为很少有人可以访问超出范围的索引.Swift设计者在访问越界索引时以牺牲运行时异常为代价,选择了较少的可选解包.崩溃比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) ->
Element?
对于那些喜欢更传统语法的人的扩展:
extension Array { func item(at index: Int) -> Element? { return indices.contains(index) ? self[index] : nil } }
我发现安全的数组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]) } }