原文: 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添加越来越多的特性。在拥有了非常多通用目的的函数后,它变得非常的臃肿。这里我列出了两个主要的问题:
- 我们的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开发更通用的基于数据的扩展。