今天我们将会谈论使用协议作为我们的视图控制器的可组合部分。协议和协议扩展是我第二喜欢的Swift的特性,仅次于可选值。它帮助我们创建高可用的可组合与可重用的代码,而不需要继承。多年来,我们一直使用继承作为黄金编程标准。但是它真的好么?让我们看一下一个简单的BaseViewController,我们在每一个文件中都使用它。
import UIKitclass 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添加越来越多的特性。在拥有了非常多通用目的的函数后,它变得非常的臃肿。这里我列出了两个主要的问题:
- 我们的BaseViewController在同一个地方实现了所有的特性,打破了单一职责原则。过了一段时间后,它将变得非常的臃肿,这会变得难以理解,并且难以进行覆盖测试。
- 在我们的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开发更通用的基于数据的扩展。