原文: Using protocols as composable extensionsgit
今天咱們將會談論使用協議做爲咱們的視圖控制器的可組合部分。協議和協議擴展是我第二喜歡的Swift的特性,僅次於可選值。它幫助咱們建立高可用的可組合與可重用的代碼,而不須要繼承。多年來,咱們一直使用繼承做爲黃金編程標準。可是它真的好麼?讓咱們看一下一個簡單的BaseViewController,咱們在每個文件中都使用它。github
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添加愈來愈多的特性。在擁有了很是多通用目的的函數後,它變得很是的臃腫。這裏我列出了兩個主要的問題:編程
協議擴展特性是在Swift 2.0版本的時候發佈的,而且攜帶了非誠強力的協議類型,它聲明瞭一種新的變成範式:面向協議編程。我建議你去看WWDC的關於協議與協議擴展的視頻。app
讓咱們回到咱們的主題。協議怎麼能幫助咱們解決問題?讓咱們開始聲明一個ActivityPresentable協議來呈現和消失一個活動指示器。ide
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的功能。下面是使用這些協議的例子:ui
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
presentActivity()
}
}
extension ViewController: ActivityPresentable, ErrorPresentable {}
複製代碼
另外一個機會是咱們能夠輕易的去忽略協議的默認實現,去爲某些ViewController實現咱們自定義的視圖指示器。讓咱們看下面這個例子:spa
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
presentActivity()
}
}
extension CustomViewController: ActivityPresentable {
func presentActivity() {
// Custom activity presenting logic
}
func dismissActivity() {
}
}
複製代碼
當爲CustomViewController實現ActivityPresentable協議的時候,咱們指定presentActivity和dismissActivity方法的自定義實現。code
就像你看到的那樣,咱們能夠將協議用做ViewController類型的簡單擴展。在將來的文章中,咱們將會繼續使用協議去爲ViewController構建可重用的模塊。咱們將會接觸可關聯的類型和條件一致性功能,以便爲ViewController開發更通用的基於數據的擴展。