使用协议作为可组合扩展

原文: Using protocols as composable extensions

今天我们将会谈论使用协议作为我们的视图控制器的可组合部分。协议和协议扩展是我第二喜欢的Swift的特性,仅次于可选值。它帮助我们创建高可用的可组合与可重用的代码,而不需要继承。多年来,我们一直使用继承作为黄金编程标准。但是它真的好么?让我们看一下一个简单的BaseViewController,我们在每一个文件中都使用它。

import UIKit

class BaseViewController: UIViewController {
    private let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(activityIndicator)

        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
            ])
    }

    func presenActivity() {
        activityIndicator.startAnimating()
    }

    func dismissActivity() {
        activityIndicator.stopAnimating()
    }

    func present(_ error: Error) {
        let alert = UIAlertController(title: error.localizedDescription, message: nil, preferredStyle: .alert)
        alert.addAction(.init(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }
}
复制代码

上面的代码看起来非常的简单易用,因为大部分的ViewController在从因特网下载数据需要活动指示器,并且在数据下载的过程中出现错误时需要进行错误处理。但是我们并不止于此,并且我们在一段时间内往BaseViewController添加越来越多的特性。在拥有了非常多通用目的的函数后,它变得非常的臃肿。这里我列出了两个主要的问题:

  1. 我们的BaseViewController在同一个地方实现了所有的特性,打破了单一职责原则。过了一段时间后,它将变得非常的臃肿,这会变得难以理解,并且难以进行覆盖测试。
  2. 在我们的app中,所有的ViewController都继承自BaseViewController,并且使用所有这些功能。假如有个bug在BaseViewController,那么我们的app的所有ViewController都会有这个bug,即使ViewController没有使用这个BaseViewController的错误功能。

使用协议来救援

协议扩展特性是在Swift 2.0版本的时候发布的,并且携带了非诚强力的协议类型,它声明了一种新的变成范式:面向协议编程。我建议你去看WWDC的关于协议与协议扩展的视频

让我们回到我们的主题。协议怎么能帮助我们解决问题?让我们开始声明一个ActivityPresentable协议来呈现和消失一个活动指示器。

protocol ActivityPresentable {
    func presentActivity()
    func dismissActivity()
}

extension ActivityPresentable where Self: UIViewController {
    func presentActivity() {
        if let activityIndicator = findActivity() {
            activityIndicator.startAnimating()
        } else {
            let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
            activityIndicator.startAnimating()
            view.addSubview(activityIndicator)

            activityIndicator.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                activityIndicator.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
                activityIndicator.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
                ])
        }
    }

    func dismissActivity() {
        findActivity()?.stopAnimating()
    }

    func findActivity() -> UIActivityIndicatorView? {
        return view.subviews.compactMap { $0 as? UIActivityIndicatorView }.first
    }
}
复制代码

我们提取presentActivity和dismissActivity方法到一个特定的协议类型中。对于采用此协议的Type为ViewController的情况,我们通过协议扩展来添加默认的实现。它给我们一个可以在我们的协议中使用ViewController的方法和属性的机会。

让我们为错误处理逻辑做相同的操作。

protocol ErrorPresentable {
    func present(_ error: Error)
}

extension ErrorPresentable where Self: UIViewController {
    func present(_ error: Error) {
        let alert = UIAlertController(title: error.localizedDescription, message: nil, preferredStyle: .alert)
        alert.addAction(.init(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }
}
复制代码

现在我们有了两个可重用的协议类型,它们都遵守单一职责原则。我们可以为任何需要这些功能的ViewController添加这个扩展。好的事情是我们可以为需要的ViewController添加单一的扩展,并且不用继承所有的BaseViewController的功能。下面是使用这些协议的例子:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        presentActivity()
    }
}

extension ViewController: ActivityPresentable, ErrorPresentable {}
复制代码

另一个机会是我们可以轻易的去忽略协议的默认实现,去为某些ViewController实现我们自定义的视图指示器。让我们看下面这个例子:

class CustomViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        presentActivity()
    }
}

extension CustomViewController: ActivityPresentable {
    func presentActivity() {
        // Custom activity presenting logic
    }

    func dismissActivity() {

    }
}
复制代码

当为CustomViewController实现ActivityPresentable协议的时候,我们指定presentActivity和dismissActivity方法的自定义实现。

小结

就像你看到的那样,我们可以将协议用作ViewController类型的简单扩展。在未来的文章中,我们将会继续使用协议去为ViewController构建可重用的模块。我们将会接触可关联的类型和条件一致性功能,以便为ViewController开发更通用的基于数据的扩展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值