Swift Protocol 詳解 - 協議&面向協議編程

以前一個帖子我總結了本身秋招面試經歷,做爲一個Swift開發者,有一個很是高頻的問題就是:你以爲Swift相比於其餘語言(或者OC來講)的特色和優點是什麼?做爲一個見識短淺的小白來講,這個問題實在是不知如何下手啊。這篇文章,也只是從一個小的角度切入,談一談Swift中的協議Protocol 和 Protocol Oriented Programming。html

面向協議編程 (Protocol Oriented Programming) 是 Apple 在 2015 年 WWDC 上提出的 Swift 的一種編程範式。下面將從Protocol的基本用法開始講起,最後再分析Protocol在下降代碼耦合性方面的優點git

Protocol - 協議基本用法

《 The Swift Programming Language 》github

Protocol 基礎語法

  1. 屬性要求 :
    • { get set } :指定讀寫屬性
    • static/class:指定類型屬性
  2. 方法要求:
    • static/class:指定類方法
    • mutating:要求實現可變方法(針對值類型的實例方法,能夠在該方法中修改它所屬的實例以及實例的任意屬性的值)
  3. 構造器要求:
    • 在遵循協議的類中,必須使用required關鍵字修飾,保證其子類也必須提供該構造器的實現。(除非有final修飾的類,能夠不用required,由於不會再有子類)

Protocol 做爲類型

  1. 做爲類型:表明遵循了該協議的某個實例(實際上就是某個實例遵循了協議)
  2. 協議類型的集合:let A: [someProtocol],遵照某個協議的實例的集合
  3. Delegate 委託設計模式:定義協議來封裝那些須要被委託的功能

Protocol 間的關係

  1. 協議的繼承:協議可繼承
  2. 協議的合成:使用&關鍵字,同時遵循多個協議
  3. 協議的一致性:使用isas?as!進行一致性檢查
  4. 類專屬協議:協議繼承時使用class關鍵字,限制該協議職能被類繼承

optional & @objc 關鍵字

可選協議:使用optional修飾屬性、函數、協議自己,同時全部option必須被@objc修飾,協議自己也必須使用@objc,只能被Objective-C的類或者@objc的類使用面試

extension 關鍵字

  • (對實例使用)令已有類型遵循某個協議
  • (對協議使用)可遵循其餘協議,增長協議一致性
  • (對協議使用)提供默認實現
  • (搭配where對協議使用)增長限制條件

Classes 類 - 特色和問題

類(Class) 是面向對象編程之中的重要元素,它表明的是一個共享相同結構和行爲的對象的集合編程

  • Classes 能夠作的事:
    • Encapsulation 封裝:表現爲對外提供接口,隱藏具體邏輯,保證類的高內聚
    • Access Control 訪問控制:依賴於類的修飾符(如public、private),保證隔離性
    • Abstraction 抽象:提取具備相似特性的事物,進行建模
    • NameSpace 命名空間:避免不一樣做用域中,同名變量、函數發生衝突
    • Expressive Syntax 豐富的語法
    • Extensibility 可拓展性:可繼承、可重寫等等

Classes 的問題:

1. Implicit Sharing 隱式共享:

可能會致使大量保護性拷貝(Defensive Copy),致使效率下降;也有可能發生競爭條件(race condition),出現不可預知的錯誤;爲了不race condition,須要使用鎖(Lock),可是這更會致使代碼效率下降,而且有可能致使死鎖(Dead Lock)swift

2. Inheritance All 所有繼承:

因爲繼承時,子類將繼承父類所有的屬性,因此有可能致使子類過於龐大,邏輯過於複雜。尤爲是當父類具備存儲屬性(stored properties)的時候,子類必須所有繼承,而且當心翼翼得初始化,避免損壞父類中的邏輯。若是須要重寫(override)父類的方法,則必需要當心思考如何重寫以及什麼時候重寫。設計模式

3. Lost Type Relationships 不能反應類型關係:

上圖中,兩個類(Label、Number)擁有相同的父類(Ordered),可是在 Number 中調用 Order 類必需要使用強制解析(as!)來判斷 Other 的屬性,這樣作既不優雅,也很是容易出Bug(若是 Other 碰巧爲Label類)數據結構

Coupling or dependency 耦合性

採用面向協議編程的方式,能夠在必定程度上下降代碼的耦合性。app

耦合性是一種軟件度量,是指一程序中,模塊及模塊之間信息或參數依賴的程度。高耦合性將使得維護成本變高,同時下降代碼可複用程度。低耦合性是結構良好程序的特性,低耦合性程序的可讀性及可維護性會比較好。ide

耦合級別

圖示是耦合程度由高到低,可粗略分爲五個級別:

  • Content coupling 內容耦合:又稱病態耦合,一個模塊直接使用另外一個模塊的內部數據。
  • Common coupling 公共耦合:又稱全局耦合,指經過一個公共數據環境相互做用的那些模塊間的耦合。公共耦合的複雜程序隨耦合模塊的個數增長而增長。
  • Control coupling 控制耦合:指一個模塊調用另外一個模塊時,傳遞的是控制變量(如開關、標誌等),被調模塊經過該控制變量的值有選擇地執行塊內某一功能。
  • Stamp coupling 特徵耦合/標記耦合:又稱數據耦合,幾個模塊共享一個複雜的數據結構。
  • Data coupling 數據耦合:是指模塊藉由傳入值共享數據,每個數據都是最基本的數據,並且只分享這些數據(例如傳遞一個整數給計算平方根的函數)

高耦合性帶來的問題

  • 維護代價大:修改一個模塊時可能產生漣漪效應,其餘模塊的內部邏輯也須要修改
  • 結構不清晰:因爲模塊間依賴性太多,因此在模塊的組合時須要消耗更多精力
  • 可複用性低:每個模塊的依賴模塊太多,致使可複用的程度下降

解耦 - Dependency Inversion Principle 依賴反轉原則

傳統的依賴關係建立在高層次上,而具體的策略設置則應用在低層次的模塊上,採用繼承的方式實現。依賴反轉原則(DIP)是指一種特定的解耦方式,使得高層次的模塊不依賴於低層次的模塊的實現細節,依賴關係被顛倒(反轉),從而使得低層次模塊依賴於高層次模塊的需求抽象。

DIP 規定:

  • 高層次的模塊不該該依賴於低層次的模塊,二者都應該依賴於抽象接口。
  • 抽象接口不該該依賴於具體實現。而具體實現則應該依賴於抽象接口。

舉一個簡單而經典的例子 -- 檯燈和按鈕

第一幅圖爲傳統的實現方式,依賴關係被建立直接在高層次對象(Button)上,當你須要改變低層次對象(Lamp)時,你必需要同時更改其父類(Button),若是此時有多個低層次的對象繼承自父類(Button),那麼更改其父類就變得十分困難。而第二幅圖是符合DIP原則的方式,高層對象(Button)把需求抽象爲一個抽象接口(ButtonServer),而具體實現(Lamp)依賴於這個抽象接口。同時,當須要實現多個底層對象時,只須要在具體實現時進行不一樣的實現便可。

解耦 - Protocol Oriented Programming 面向協議編程

面向協議編程中,Protocol 實際上就是 DIP 中的抽象接口。經過以前的講解,採用面向協議的方式進行編程,便是對依賴反轉原則 DIP 的踐行,在必定程度上下降代碼的耦合性,避免耦合性太高帶來的問題。下面經過一個具體實例簡單講解一下:

首先是高層次結構的實現,建立EmmettBrown的類,而後聲明瞭一個需求(travelInTime方法)。

// 高層次實現 - EmmettBrown
final class EmmettBrown {
	private let timeMachine: TimeTraveling
	init(timeMachine: TimeTraveling) {
		self.timeMachine = timeMachine
	}
	func travelInTime(time: TimeInterval) -> String {
		return timeMachine.travelInTime(time: time)
	}
}
複製代碼

採用 Protocol 定義抽象接口 travelInTime,低層次的實現將須要依賴這個接口。

// 抽象接口 - 時光旅行
protocol TimeTraveling {
    func travelInTime(time: TimeInterval) -> String
}
複製代碼

最後是低層次實現,建立DeLorean類,經過遵循TimeTraveling協議,完成TravelInTime抽象接口的具體實現。

// 低層次實現 - DeLorean
final class DeLorean: TimeTraveling {
	func travelInTime(time: TimeInterval) -> String {
		return "Used Flux Capacitor and travelled in time by: \(time)s"
	}
}
複製代碼

使用的時候只須要建立相關類便可調用其方法。

// 使用方式
let timeMachine = DeLorean()
let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)
複製代碼

Delegate - 利用 Protocol 解耦

Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type.

委託(Delegate)是一種設計模式,表示將一個對象的部分功能轉交給另外一個對象。委託模式能夠用來響應特定的動做,或者接收外部數據源提供的數據,而無需關心外部數據源的類型。部分狀況下,Delegate 比起自上而下的繼承具備更鬆的耦合程度,有效的減小代碼的複雜程度。

那麼 Deleagte 和 Protocol 之間是什麼關係呢?在 Swift 中,Delegate 就是基於 Protocol 實現的,定義 Protocol 來封裝那些須要被委託的功能,這樣就能確保遵循協議的類型能提供這些功能。

Protocol 是 Swift 的語言特性之一,而 Delegate 是利用了 Protocol 來達到解耦的目的。

Delegate 使用實例:

//定義一個委託
protocol CustomButtonDelegate: AnyObject{
    func CustomButtonDidClick()
}
 
class ACustomButton: UIView {
    ...
    weak var delegate: ButtonDelegate?
    func didClick() {
        delegate?.CustomButtonDidClick()
    }
}

// 遵循委託的類
class ViewController: UIViewController, CustomButtonDelegate {
    let view = ACustomButton()
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        view.delegate = self
    }
    func CustomButtonDidClick() {
        print("Delegation works!")
    }
}
複製代碼

代碼說明

如前所述,Delegate 的原理其實很簡單。ViewController 會將 ACustomButtondelegate 設置爲本身,同時本身遵循、實現了 CustomButtonDelegate 協議中的方法。這樣在後者調用 didClick 方法的時候會調用 CustomButtonDidClick 方法,從而觸發前者中對應的方法,從而打印出 Delegation works!

循環引用

咱們注意到,在聲明委託時,咱們使用了 weak 關鍵字。目的是在於避免循環引用。ViewController 擁有 view,而 view.delegate 又強引用了 ViewController,若是不將其中一個強引用設置爲弱引用,就會形成循環引用的問題。

AnyObject

定義委託時,咱們讓 protocol 繼承自 AnyObject。這是因爲,在 Swift 中,這表示這一個協議只能被應用於 class(而不是 struct 和 enum)。

實際上,若是讓 protocol 不繼承自任何東西,那也是能夠的,這樣定義的 Delegate 就能夠被應用於 class 以及 struct、enum。因爲 Delegate 表明的是遵循了該協議的實例,因此當 Delegate 被應用於 class 時,它就是 Reference type,須要考慮循環引用的問題,所以就必需要用 weak 關鍵字。

可是這樣的問題在於,當 Delegate 被應用於 struct 和 enum 時,它是 Value type,不須要考慮循環引用的問題,也不能被使用 weak 關鍵字。因此當 Delegate 未限定只能用於 class,Xcode 就會對 weak 關鍵字報錯:'weak' may only be applied to class and class-bound protocol types

那麼爲何不使用 class 和 NSObjectProtocol,而要使用 AnyObject 呢?NSObjectProtocol 來自 Objective-C,在 pure Swift 的項目中並不推薦使用。class 和 AnyObject 並無什麼區別,在 Xcode 中也能達到相同的功能,可是官方仍是推薦使用 AnyObject。

參考資料

相關文章
相關標籤/搜索