我想每个开发人员都至少经历过一次index-out-of-bounds
的报错。就是数组越界,这个大家都懂,就不过多介绍了。下面是个数组越界的例子:
let values = ["A", "B", "C"]
values[0] // A
values[1] // B
values[2] // C
values[3] // Fatal error: Index out of range
既然是下标超过了数组的大小,那我们在取值之前,先检查下标是否超过数组大小。让我们来看下面的几种方案:
-
通过 if 来判断下标
if 2 < values.count {
values[2] // "C"
}
if 3 < values.count {
values[3] // 不会走到这里
}
虽然也可以,但显的就很重复繁琐,每次取值之前都要判断一遍下标。
-
定义公共函数
既然每次都要检查下标,那就把检查下标的逻辑放在一个函数里
func getValue<T>(in elements: [T], at index: Int) -> T? {
guard index >= 0 && index < elements.count else {
return nil
}
return elements[index]
}
let values = ["A", "B", "C"]
getValue(in: values, at: 2) // "C"
getValue(in: values, at: 3) // nil
不仅使用泛型支持了任何类型的元素,当数组越界时,还很贴心的返回了 nil,防止崩溃。
虽然很贴心,但每次取值都要把原数组传进去,显的就很冗余。
-
extension
既然每次都要传入数组很冗余,那就把数组的参数给去掉。我们知道 Swift 一个很强大的特性就是 extension,我们给 Array定义个 extension,并把这个函数添加进去。
extension Array {
func getValue(at index: Int) -> Element? {
guard index >= 0 && index < self.count else {
return nil
}
return self[index]
}
}
let values = ["A", "B", "C"]
values.getValue(at: 2) // "C"
values.getValue(at: 3) // nil
-
subscript
虽然看起来好很多了,但可不可以像原生的取值一样, 一个[]就搞定了
extension Array {
subscript (safe index: Int) -> Element? {
guard index >= 0 && index < self.count else {
return nil
}
return self[index]
}
}
values[safe: 2] // "C"
values[safe: 3] // nil
自定义的[safe: 2]和原生的 [2]非常的接近了。但自定义的提供了数据越界保护机制。
-
应用到 Collection
既然这么棒,岂能数组一人独享,我们把它应用到所有 Collection 协议。看起来是不是很优雅~😉
extension Collection {
public subscript (safe index: Self.Index) -> Iterator.Element? {
(startIndex ..< endIndex).contains(index) ? self[index] : nil
}
}