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

2,384 阅读2分钟

这是我参与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,强制解包就崩了

image.png

所以需要换个思路,我们给抽出来一个函数:

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 用法,大家慢慢探索吧,哈哈哈