使用協議做爲可組合擴展

原文: 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添加愈來愈多的特性。在擁有了很是多通用目的的函數後,它變得很是的臃腫。這裏我列出了兩個主要的問題:編程

  1. 咱們的BaseViewController在同一個地方實現了全部的特性,打破了單一職責原則。過了一段時間後,它將變得很是的臃腫,這會變得難以理解,而且難以進行覆蓋測試。
  2. 在咱們的app中,全部的ViewController都繼承自BaseViewController,而且使用全部這些功能。假若有個bug在BaseViewController,那麼咱們的app的全部ViewController都會有這個bug,即便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開發更通用的基於數據的擴展。

相關文章
相關標籤/搜索