这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
废话不多说,让我们直接开始 5 个让 Swift 更优雅的扩展的第二部分,第一部分的传送门:5 个让 Swift 更优雅的扩展——Pt.1
3. 自定义操作符
先来看一段代码,定义了一个 weak
变量 someViewRef
,然后创建、设置属性、添加到父视图上。
可以正常执行,还是会有什么问题?
class ViewController: UIViewController {
private weak var someViewRef: UIView?
override func viewDidLoad() {
super.viewDidLoad()
someViewRef = UIView(frame: CGRect(x: 100, y: 50, width: 100, height: 100))
someViewRef?.backgroundColor = .red
view.addSubview(someViewRef!)
}
}
相信大家一眼就看出来了,执行到第一句 someViewRef = UIView(frame: CGRect(x: 100, y: 50, width: 100, height: 100))
的时候就报错了
- 第一句:因为 someViewRef 是用 weak 修饰的,所以立马会被释放
- 第三句:因为被释放,所以 someViewRef 是 nil,强制解包就崩了
所以需要换个思路,我们给抽出来一个函数:
class ViewController: UIViewController {
private weak var someViewRef: UIView?
override func viewDidLoad() {
super.viewDidLoad()
let someView = assign(someViewRef: &someViewRef, someView: UIView(frame: CGRect(x: 100, y: 50, width: 100, height: 100)))
view.addSubview(someView)
someViewRef?.backgroundColor = .red
}
func assign(someViewRef: inout Optional<UIView>, someView: UIView) -> UIView {
someViewRef = someView
return someView
}
}
这样一个红色小方块就显示出来了。不过还是有点限制,它只适用于 UIView,我们给它上泛型,把格局打开:
func assign<T>(target: inout Optional<T>, value: T) -> T {
target = value
return value
}
这时候我们看下调用:
let someView = assign(target: &someViewRef, value: UIView(frame: CGRect(x: 100, y: 50, width: 100, height: 100)))
发现
- 每次都要传入目标变量的引用,
&someViewRef
,略麻烦。 - 而且传参 value 时,都是先创建 UIView 实例,再传入,有隐患,我们需要让它延迟执行,进入 assign 方法内部时再创建
大招 CD 已好,我们定义个操作符,像 +
-
一样,再利用 @autoclosure
的特性,就有了下面的实现:
infix operator <-
public func <- <T>(target: inout T?, value: @autoclosure () -> T) -> T {
let val = value()
target = val
return val
}
再来看下调用:
let someView = someViewRef <- UIView()
perfect~
4. 统计集合中某个元素的个数
我想大家都遇到过这样的需求,从网络请求数据后,得到一个数组,然后我们要统计里面某个元素有多少个,来看看下面几个常用的方式,你有没有用到过?
let array = ["A", "A", "B", "A", "C"]
// 1.
var count = 0
for value in array {
if value == "A" {
count += 1
}
}
// 2.
count = 0
for value in array where value == "A" {
count += 1
}
// 3.
count = array.filter { $0 == "A" }.count
// 4...
我们知道获取集合的总个数用.count
就可以。为了项目中代码更加统一和可维护性,与其有上面那么多种写法,不如也定义个类似的扩展:count(where:)
extension Sequence where Element: Equatable {
func count(where isIncluded: (Element) -> Bool) -> Int {
self.filter(isIncluded).count
}
}
我们直接给 Sequence 添加扩展,这样格局就又打开了,像 ArraySlice,也支持。
["A", "A", "B"].count(where: { $0 == "A" }) // 2
["B", "A", "B"].dropLast(1) // --> ArraySlice<String>
.count(where: { $0 == "B" }) // 1
5. 去除集合中指定的重复的对象
日常开发中,我想会遇到这样的需求,从网络请求数据得到一个数组,现在需要删除其中重复的元素,同时保证原有的顺序。先看第一种方法:
- 第一种,内部元素是可哈希的
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var seen = Set<Element>()
return filter { seen.insert($0).inserted }
}
}
[ 1, 2, 3, 1, 2 ].uniqued() // [ 1, 2, 3 ]
内部使用了 Set 进行 contains 查找,比数组的 contains (_:)
方法时间复杂度要低。
- 第二种,内部元素不是可哈希的
如果数组内部的元素是解析的 model,而且没有遵守 Hashable,上面的方法就用不了了。
extension Sequence {
func uniqued(comparator: (Element, Element) -> Bool) -> [Element] {
var result: [Element] = []
for element in self {
if result.contains(where: {comparator(element, $0)}) {
continue
}
result.append(element)
}
return result
}
}
let article1 = ArticleModel(title: "111", content: "aa", articleID: "11111", comments: [])
let article2 = ArticleModel(title: "222", content: "aaa", articleID: "22222", comments: [])
let article3 = ArticleModel(title: "111", content: "aaaa", articleID: "33333", comments: [])
let article4 = ArticleModel(title: "333", content: "aaaaa", articleID: "44444", comments: [])
let articles = [article1, article2, article3, article4]
let newarticles = articles.uniqued(comparator: {$0.title == $1.title})
print(newarticles) //结果 article3 会被删除
- 第三种,keypath 版本
最后再来个 keypath 版本,不过是对第二种的一层封装,内部依然调用的是第二种
extension Sequence {
func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
}
}
let newarticles = articles.uniqued(\.title)
结语
通过上面提到的 5 种例子,可见 Swift 的 extension 特性非常的强大且灵活,让代码的可扩展性大大提高。
更多的 extension 用法,大家慢慢探索吧,哈哈哈