5 个让 Swift 更优雅的扩展——Pt.1

7b63ca21ffe541368fcc953f475f4fcd~tplv-k3u1fbpfcp-zoom-crop-mark:1304:1304:1304:734.awebp?

这是我参与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

如果觉得对你有帮助,不妨在项目中试试吧~

链接:https://juejin.cn/post/7026271045652840461

0 个评论

要回复文章请先登录注册