如果我在Swift中有一个数组,并尝试访问超出范围的索引,则会出现一个不足为奇的运行时错误:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
Run Code Online (Sandbox Code Playgroud)
但是,我会想到Swift带来的所有可选链接和安全性,这样做会很简单:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Run Code Online (Sandbox Code Playgroud)
代替:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
Run Code Online (Sandbox Code Playgroud)
但事实并非如此 - 我必须使用ol' if语句来检查并确保索引小于str.count.
我尝试添加自己的subscript()实现,但我不知道如何将调用传递给原始实现,或者不使用下标符号来访问项目(基于索引):
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Run Code Online (Sandbox Code Playgroud)
Nik*_*kin 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
}
}
Run Code Online (Sandbox Code Playgroud)
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
}
}
Run Code Online (Sandbox Code Playgroud)
感谢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
}
}
Run Code Online (Sandbox Code Playgroud)
let array = [1, 2, 3]
for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}
Run Code Online (Sandbox Code Playgroud)
Ale*_*yne 56
如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组.字典nil在访问丢失的密钥时返回,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中密钥必须在以下范围内:0to count.迭代这个范围是非常常见的,你可以绝对肯定在循环的每次迭代中都有一个真正的值.
我认为它不能以这种方式工作的原因是Swift开发人员做出的设计选择.举个例子:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"
Run Code Online (Sandbox Code Playgroud)
如果您已经知道索引存在,就像在大多数使用数组的情况下一样,这段代码很棒.但是,如果访问标可能可能返回nil,那么你已经改变了返回类型的Array的subscript方法是可选的.这会将您的代码更改为:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
// ^ Added
Run Code Online (Sandbox Code Playgroud)
这意味着每次迭代数组时都需要解包一个可选项,或者使用已知索引执行任何其他操作,因为很少有人可以访问超出范围的索引.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
}
Run Code Online (Sandbox Code Playgroud)
func get(index: Int) ->T? 需要被替换 func get(index: Int) ->Element?
DeF*_*enZ 14
虽然已经有很多次回答了这个问题,但我想在Swift编程的时尚方面提出更多的答案,用Crusty的话来说就是:"先想想protocol"
•我们想做什么?
- 只有在安全的情况下才能获得给定索引的元素Array,nil否则
•此功能应该以什么为基础?
- Array subscriptING
•哪里不从得到这个功能吗?
- 它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
}
}
Run Code Online (Sandbox Code Playgroud)
¹:不是真的,但它提出了这个想法
Saf*_*ive 13
基于Nikita Kukushkin的答案,有时您需要安全地分配数组索引以及从它们读取,即
myArray[safe: badIndex] = newValue
Run Code Online (Sandbox Code Playgroud)
所以这里是对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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
小智 10
if let index = array.checkIndexForSafety(index:Int)
Run Code Online (Sandbox Code Playgroud)let item = array[safeIndex: index]if let index = array.checkIndexForSafety(index:Int)
Run Code Online (Sandbox Code Playgroud)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
}
}
Run Code Online (Sandbox Code Playgroud)
Bra*_*key 10
扩展意味着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]
}
}
Run Code Online (Sandbox Code Playgroud)
extension Array {
subscript (safe index: Index) -> Element? {
return 0 <= index && index < count ? self[index] : nil
}
}
Run Code Online (Sandbox Code Playgroud)
以下是我为你跑的一些测试:
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?
Run Code Online (Sandbox Code Playgroud)
对于那些喜欢更传统语法的人的扩展:
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Run Code Online (Sandbox Code Playgroud)
我意识到这是一个老问题。我现在使用的是 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
Run Code Online (Sandbox Code Playgroud)
对于那些争论带有 nil 的 Sequences 的人,对于空集合返回 nil的first和last属性,您会怎么做?
我喜欢这个,因为我可以抓住现有的东西并使用它来获得我想要的结果。我也知道 dropFirst(n) 不是整个集合副本,只是一个切片。然后 first 已经存在的行为接管了我。
我发现安全的数组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)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
测验
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])
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
73267 次 |
| 最近记录: |