5 个让 Swift 更优雅的扩展——Pt.1
这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
引言
作为开发者,应该编写具有高可维护性和可扩展性的代码。我们可以通过扩展原有的功能,写出更易读,更简洁的代码。
下面就介绍 5 个日常开发中非常实用的扩展。
1. 自定义下标来安全访问数组
我想每个开发人员都至少经历过一次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
虽然看起来好很多了,但可不可以像原生的取值一样, 一个[]就搞定了呢?of course!
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
}
}
2. 平等的处理 nil 和空字符串
在处理可选值时,我们通常需要将它们与 nil 进行比较进行空检查。当为 nil 时,我们会提供一个默认值让程序继续执行。比如下面这个例子:
func unwrap(value: String?) -> String {
return value ?? "default value"
}
unwrap(value: "foo") // foo
unwrap(value: nil) // default value
但是还有种情况就是空字符串,有时,我们需要把空字符串当做 nil 的情况来处理。此时,不仅要坚持 nil,还要检查空字符串的情况
func unwrap(value: String?) -> String {
let defaultValue = "default value"
guard let value = value else {
return defaultValue
}
if value.isEmpty {
return defaultValue
}
return value
}
unwrap(value: "foo") // foo
unwrap(value: "") // default value
unwrap(value: nil) // default value
虽然也能解决问题,但依然看起来很臃肿,我们把他简化一下:
func unwrapCompressed(value val: String?) -> String {
return val != nil && !val!.isEmpty ? val! : "default value"
}
unwrapCompressed(value: "foo") // foo
unwrapCompressed(value: "") // default value
unwrapCompressed(value: nil) // default value
虽然简化了很多,但不易读,可维护性略差。
可以把空字符串先转化为 nil,再进行处理,这样就和处理 nil 的情况一致了。
public extension String {
var nilIfEmpty: String? {
self.isEmpty ? nil : self
}
}
let foo: String? = nil
if let value = foo?.nilIfEmpty {
print(value) //不会调用
}
if let value = "".nilIfEmpty {
print(value) //不会调用
}
if let value = "ABC".nilIfEmpty {
print(value) //ABC
}
总结
这里先介绍 5 个常用扩展中的其中 2 个,剩下 3 个且听下回分解啦~
- 给集合增加扩展,防止取值越界造成崩溃
- 给字符串增加扩展,让空字符串变为 nil
如果觉得对你有帮助,不妨在项目中试试吧~