Xcode 通过可选绑定在Swift中进行安全(已检查边界)数组查找?
如果我在Swift中有一个数组,并尝试访问一个超出边界的索引,则会出现一个毫不奇怪的运行时错误:Xcode 通过可选绑定在Swift中进行安全(已检查边界)数组查找?,xcode,swift,Xcode,Swift,如果我在Swift中有一个数组,并尝试访问一个超出边界的索引,则会出现一个毫不奇怪的运行时错误: var str = ["Apple", "Banana", "Coconut"] str[0] // "Apple" str[3] // EXC_BAD_INSTRUCTION 然而,我会认为,考虑到Swift带来的所有可选链接和安全性,执行以下操作将是微不足道的: let theIndex = 3 if let nonexistent = str[theIndex] { // Bounds c
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...
}
我认为这不是一个好主意。构建不会导致尝试应用越界索引的可靠代码似乎更可取
请考虑这样一个错误(如上面的代码所建议的),通过返回<代码> nIL/COD>会产生更复杂、更难处理的错误。 您可以使用与以前类似的方式进行重写,只需以自己的方式编写下标。唯一的缺点是现有代码不兼容。我认为找到一个钩子来覆盖通用的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")
}
func-ifInBounds(数组:[AnyObject],idx:Int)->AnyObject?{
返回idx
如果你真的想要这种行为,它闻起来就像你想要一个字典而不是数组。当访问缺少的键时,字典返回nil
,这是有意义的,因为很难知道键是否存在于字典中,因为这些键可以是任何键,在数组中,键必须在以下范围内:0
到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
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
}
扩展数组{
//安全地查找可能超出范围的索引,
//如果不存在,则返回nil
func get(索引:Int)->T{
如果0
需要替换为func-get(index:Int)->
元素?
在我的用例中,我已经用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:
标签检查下标扩展:对这个问题有很好的建议和解决方案,但是,我碰巧发现了一种更好的实现此功能的方法:
Swift 3.2及更新版本
Swift 3.0和3.1
哈米什想出了一个好主意
斯威夫特2
例子
在Swift 2中有效
尽管这个问题已经被回答了很多次,但我想给出一个更符合Swift编程潮流的答案,用Crasty的话说就是:“先考虑协议
s”
•我们想做什么?
-仅在安全的情况下获取数组的元素
给定索引,否则nil
获取
•此功能的实现应以什么为基础?
-数组
下标
ing
•从何处获得此功能?
-它在Swift
模块中定义了struct Array
•没有比这更一般/抽象的了?
-它采用了协议收集类型
,这也保证了它的安全性
•没有比这更一般/抽象的了?
-它还采用了可索引的协议
。•是的,听起来是我们能做的最好的了。然后我们能扩展它以拥有我们想要的功能吗?
-但是我们现在使用的类型(no
Int
)和属性(nocount
)非常有限!•这就足够了。Swift的stdlib做得很好;) 不是真的,但它给出了一个想法
- 因为数组可能存储nil值,所以如果数组[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)
}
}
}
}
斯威夫特4
为那些喜欢更传统语法的人提供的扩展:
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
为了基于Nikita Kukushkin的答案,有时需要安全地分配数组索引并从中读取,即
myArray[safe: badIndex] = newValue
这里有一个更新
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)
}
}
extension Indexable {
public subscript(safe safeIndex: Index) -> _Element? {
return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
}
}
let item = array[safeIndex: index]
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
}
}
/**
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])
}
}
extension Array {
subscript (safe index: Index) -> Element? {
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? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
myArray[safe: badIndex] = newValue
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
}
}
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}
let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")
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,
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
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>
// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
if index >= 0 && index < array.count {
print(array[index])
}