1.Swift中的Protocol
什么是Protocol?
Protocol是Swift中的一種自定義類型驰唬,可以使用protocol定義某種約定顶岸,而不是某一種類型,一般用于表示某種類型的共性叫编。
Protocol 用法
定義一個(gè)protocol
protocol PersonProtocol {
func getName()
func getSex()
}
某個(gè)class辖佣、struct或者enum要遵守這種約定的話,需要實(shí)現(xiàn)約定的方法
struct Person: PersonProtocol {
func getName() {
print("MelodyZhy")
}
func getSex() {
print("boy")
}
}
protocol中的約定方法搓逾,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
protocol中也可以定義屬性凌简,但必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(get set)
protocol PersonProtocol {
// 我們也可以在protocol中定義屬性
// ?必須明確指定該屬性支持的操作:只讀(get)或者是可讀寫(get set)
var height: Int { get set }
func getName()
func getSex()
// protocol中的約定方法,當(dāng)方法中有參數(shù)時(shí)是不能有默認(rèn)值的
// ? Default argument not permitted in a protocol method
// func getAge(age: Int = 18)
func getAge(age: Int)
}
雖然height在protocol中是一個(gè)computed property,但在遵守該約定的類型中可以簡(jiǎn)單的定義成一個(gè)stored property
當(dāng)protocol中定義了一個(gè)只讀屬性恃逻,其實(shí)我們也可以在遵守該約定的類型中完成該屬性的可讀可寫
protocol PersonProtocol {
var height: Int { get set }
var weight: Int { get }
func getName()
func getSex()
func getAge(age: Int)
}
struct Person: PersonProtocol {
var height = 178
var weight = 120
func getName() {
print("MelodyZhy")
}
func getSex() {
print("boy")
}
func getAge(age: Int) {
print("age = \(age)")
}
}
var person = Person()
person.height // 178
person.height = 180
person.height // 180
person.weight // 120
// 可以更改(但在以前版本的Swift中是不能直接這樣更改的
// 需要借助一個(gè)內(nèi)部的stored property
// 然后把這個(gè)屬性設(shè)計(jì)成一個(gè)computed property實(shí)現(xiàn) get 和 set 方法)
person.weight = 130 // 130
// 當(dāng)我們把person從Person轉(zhuǎn)換成PersonProtocol時(shí) 他就是只讀的了
// ?Cannot assign to property: 'weight' is a get-only property
(person as PersonProtocol).weight = 120
如何定義可選的protocol屬性或者方法?
@objc protocol PersonProtocol {
optional var height: Int { get set }
optional var weight: Int { get }
optional func getName()
optional func getSex()
optional func getAge(age: Int)
}
class Person: PersonProtocol {
// 如果想提供可選的約定方法或者屬性那么只能定義@objc的protocol
// 并且這種約定只能class能遵守
}
protocol可以繼承藕施,當(dāng)然struct寇损、class、enum都可以同時(shí)遵守多個(gè)約定
// 例如:
protocol PersonProtocol {
var height: Int { get set }
var weight: Int { get }
func getName()
func getSex()
func getAge(age: Int)
}
protocol Engineer: PersonProtocol {
var good: Bool { get }
}
protocol Animal {
}
struct Person: Engineer, Animal {
// 省略了該實(shí)現(xiàn)約定的方法和屬性
}
2.protocol extension
protocol extension 最關(guān)鍵的一點(diǎn)就是能在 protocol extension 方法中獲取 protocol 的屬性裳食,因?yàn)镾wift編譯器知道任何一個(gè)遵守 protocol 的自定義類型矛市,一定會(huì)定義這個(gè) protocol 約定的各種屬性,既然這樣我們就可以在 protocol extension 中添加默認(rèn)的實(shí)現(xiàn)了诲祸。這也是為什么會(huì)有 protocol oriented programming 這個(gè)概念浊吏,但這時(shí)候肯定會(huì)有人說我通過面對(duì)對(duì)象的編程方式也可以實(shí)現(xiàn)而昨,但為什么要用遵守 protocol 的方法呢,這個(gè)要等到了解 extension 中的 type constraints 后解釋...
先看一個(gè)通過 protocol extension 添加默認(rèn)實(shí)現(xiàn)的代碼例子
// 定義一個(gè)人屬性的 protocol
protocol PersonProperty {
var height: Int { get } // cm
var weight: Double { get } // kg
// 判斷體重是否合格的函數(shù)
func isStandard() -> Bool
}
extension PersonProperty {
// 給 protocol 添加默認(rèn)的實(shí)現(xiàn)
func isStandard() -> Bool {
return self.weight == Double((height - 100)) * 0.9
}
// 給 protocol 添加默認(rèn)屬性
var isPerfectHeight: Bool {
return self.height == 178
}
}
struct Person: PersonProperty {
var height: Int
var weight: Double
// 如果自定義類型里面創(chuàng)建了遵守的 protocol 中的方法
// 那么他將覆蓋 protocol 中的方法
// func isStandard() -> Bool {
// return true
// }
}
// 創(chuàng)建遵守 PersonProperty 的自定義類型
let p = Person(height: 178, weight: 61.5)
// 那么 p 這個(gè)自定義類型 天生就有判斷這個(gè)人身高體重是否合格的方法
p.isStandard() // false
// 同樣天生具有判斷是否是 Perfect Height 的屬性
p.isPerfectHeight // true
protocol extension 中的 type constraints
這相當(dāng)于給 protocol extension 中的默認(rèn)實(shí)現(xiàn)添加限定條件,寫法如下
// 運(yùn)動(dòng)因素的 protocol
protocol SportsFactors {
// 運(yùn)動(dòng)量
var sportQuantity: Double { get }
}
// 下面這種寫法就用到了 extension 中的 type constraints
// 意思是 只有同時(shí)遵守了 SportsFactors 和 PersonProperty 時(shí)
// 才使 PersonProperty 獲得擴(kuò)展 并提供帶有 sportQuantity 屬性的 isStandard 方法
extension PersonProperty where Self: SportsFactors {
func isStandard() -> Bool {
// 隨意寫的算法 不要在意
return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
}
}
protocol oriented programming 的優(yōu)點(diǎn)
1找田、首先繼承是 class 專有的歌憨,所以它不能用來擴(kuò)展其他類型,但 protocol 是沒有這種局限性的
2墩衙、試想一下务嫡,上面的代碼你用面對(duì)對(duì)象的編程方式的話可能你就需要多一個(gè)運(yùn)動(dòng)量的屬性,同時(shí)也要修改 isStandard 函數(shù)漆改,一切看起來特別自然心铃,隨著后續(xù)需求的更改可能會(huì)有更多因素影響是否是合格的體重,那么這時(shí)候你就會(huì)在不知不覺中將你代碼的耦合度成倍提高挫剑,其實(shí)對(duì)于這個(gè)類來說去扣,他完全不需要知道是否是合格體重的計(jì)算細(xì)節(jié),所以我們完全可以把這些類型無關(guān)的細(xì)節(jié)從類型定義上移出去樊破,用一個(gè) protocol 封裝好這些細(xì)節(jié)愉棱,然后讓其成為這個(gè)類型的一種修飾,這就是POP的核心思想捶码。
3羽氮、當(dāng)有多種因素制約是否是合格體重時(shí),我們可以用多個(gè) protocol 來對(duì)該類型進(jìn)行修飾惫恼,每一種修飾的相關(guān)細(xì)節(jié)档押,我們都在對(duì)應(yīng)的 protocol extension 中單獨(dú)的封裝起來,這樣就大大降低了代碼的耦合度祈纯,同時(shí)代碼的可維護(hù)性也得到了相應(yīng)的提高令宿。swift標(biāo)準(zhǔn)庫(kù)中大部分都是用這種思想構(gòu)建的。**
5.Protocol Oriented Programming 面向協(xié)議編程
面向協(xié)議編程中腕窥,Protocol 實(shí)際上就是 DIP 中的抽象接口粒没。通過之前的講解,采用面向協(xié)議的方式進(jìn)行編程簇爆,即是對(duì)依賴反轉(zhuǎn)原則 DIP 的踐行癞松,在一定程度上降低代碼的耦合性,避免耦合性過高帶來的問題入蛆。下面通過一個(gè)具體實(shí)例簡(jiǎn)單講解一下:
首先是高層次結(jié)構(gòu)的實(shí)現(xiàn)响蓉,創(chuàng)建EmmettBrown的類,然后聲明了一個(gè)需求(travelInTime方法)哨毁。
// 高層次實(shí)現(xiàn) - 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枫甲,低層次的實(shí)現(xiàn)將需要依賴這個(gè)接口。
// 抽象接口 - 時(shí)光旅行
protocol TimeTraveling {
func travelInTime(time: TimeInterval) -> String
}
最后是低層次實(shí)現(xiàn),創(chuàng)建DeLorean類想幻,通過遵循TimeTraveling協(xié)議粱栖,完成TravelInTime抽象接口的具體實(shí)現(xiàn)。
// 低層次實(shí)現(xiàn) - DeLorean
final class DeLorean: TimeTraveling {
func travelInTime(time: TimeInterval) -> String {
return "Used Flux Capacitor and travelled in time by: \(time)s"
}
}
使用的時(shí)候只需要?jiǎng)?chuàng)建相關(guān)類即可調(diào)用其方法脏毯。
// 使用方式
let timeMachine = DeLorean()
let mastermind = EmmettBrown(timeMachine: timeMachine)
mastermind.travelInTime(time: -3600 * 8760)
Delegate - 利用 Protocol 解耦
委托(Delegate)是一種設(shè)計(jì)模式闹究,表示將一個(gè)對(duì)象的部分功能轉(zhuǎn)交給另一個(gè)對(duì)象。委托模式可以用來響應(yīng)特定的動(dòng)作抄沮,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù)跋核,而無需關(guān)心外部數(shù)據(jù)源的類型。部分情況下叛买,Delegate 比起自上而下的繼承具有更松的耦合程度砂代,有效的減少代碼的復(fù)雜程度。
那么 Deleagte 和 Protocol 之間是什么關(guān)系呢率挣?在 Swift 中刻伊,Delegate 就是基于 Protocol 實(shí)現(xiàn)的,定義 Protocol 來封裝那些需要被委托的功能椒功,這樣就能確保遵循協(xié)議的類型能提供這些功能捶箱。
Protocol 是 Swift 的語言特性之一,而 Delegate 是利用了 Protocol 來達(dá)到解耦的目的动漾。
Delegate 使用實(shí)例:
//定義一個(gè)委托
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
的原理其實(shí)很簡(jiǎn)單。ViewController
會(huì)將 ACustomButton
的 delegate
設(shè)置為自己旱眯,同時(shí)自己遵循晨川、實(shí)現(xiàn)了 CustomButtonDelegate
協(xié)議中的方法。這樣在后者調(diào)用 didClick
方法的時(shí)候會(huì)調(diào)用 CustomButtonDidClick
方法删豺,從而觸發(fā)前者中對(duì)應(yīng)的方法共虑,從而打印出 Delegation works!
循環(huán)引用
我們注意到,在聲明委托時(shí)呀页,我們使用了 weak
關(guān)鍵字妈拌。目的是在于避免循環(huán)引用。ViewController
擁有 view
蓬蝶,而 view.delegate
又強(qiáng)引用了ViewController
尘分,如果不將其中一個(gè)強(qiáng)引用設(shè)置為弱引用,就會(huì)造成循環(huán)引用的問題丸氛。
AnyObject
定義委托時(shí)音诫,我們讓 protocol 繼承自 AnyObject
。這是由于雪位,在 Swift 中,這表示這一個(gè)協(xié)議只能被應(yīng)用于 class(而不是 struct 和 enum)。
實(shí)際上雹洗,如果讓 protocol
不繼承自任何東西香罐,那也是可以的,這樣定義的 Delegate
就可以被應(yīng)用于 class 以及 struct
时肿、enum
庇茫。由于 Delegate
代表的是遵循了該協(xié)議的實(shí)例,所以當(dāng) Delegate
被應(yīng)用于 class
時(shí)螃成,它就是 Reference type
旦签,需要考慮循環(huán)引用的問題,因此就必須要用 weak
關(guān)鍵字寸宏。
但是這樣的問題在于宁炫,當(dāng) Delegate
被應(yīng)用于struct
和 enum
時(shí),它是 Value type
氮凝,不需要考慮循環(huán)引用的問題羔巢,也不能被使用 weak
關(guān)鍵字。所以當(dāng)Delegate
未限定只能用于 class
罩阵,Xcode
就會(huì)對(duì) weak
關(guān)鍵字報(bào)錯(cuò):'weak' may only be applied to class and class-bound protocol types
delegate只能用于類中
定義協(xié)議的格式
編寫協(xié)議的格式:
protocol 協(xié)議名字 : 基協(xié)議 { //當(dāng)然也可以不遵守基協(xié)議
//方法的聲明
}
例:定義一個(gè)買票的協(xié)議
protocol buyTicketProtocol {
func buyTicket() -> Void
}
Tips:
如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類也必須要繼承NSObject這個(gè)類
遵守協(xié)議的格式
一個(gè)類若要遵守一個(gè)協(xié)議,只需要在自己所繼承的父類后面寫上要遵守的協(xié)議名并以逗號(hào)","隔開,如果這個(gè)類無需繼承,那么直接在冒號(hào)后面寫上協(xié)議的名字就好
遵守協(xié)議的格式:
class Person : NSObject,SportProtocol{}
例:定義一個(gè)會(huì)買票的黃牛類
class Tout : buyTicketProtocol { //無繼承類遵守協(xié)議
func buyTicket() {
print("here's your ticket")
}
}
Tips:
Swift中的基協(xié)為NSObjectProtocol,這與OC中的基協(xié)議(NSObject)有些不同
協(xié)議的繼承
- 上面有提到,當(dāng)我們自定義一個(gè)協(xié)議的時(shí)候可以選擇讓這個(gè)協(xié)議繼承自NSObjectProtocol,不單單如此,自定義的協(xié)議也可以遵守另外一個(gè)協(xié)議哦,基本格式如下:
protocol showTicketNumberProtocol { //展示票號(hào)
func showTicketNumber() -> Void
}
protocol buyTicketProtocol : showTicketNumberProtocol { //買票
func buyTicket() -> Void
}
- 如果一個(gè)類遵循了一個(gè)含有繼承的協(xié)議,那么這個(gè)類就必須實(shí)現(xiàn)這個(gè)協(xié)議鏈中所有的必須實(shí)現(xiàn)的函數(shù),否則編譯報(bào)錯(cuò)
class Tout : buyTicketProtocol {
func buyTicket() {
print("here's your ticket")
}
func showTicketNumber() { //必須實(shí)現(xiàn)buyTicketProtocol所繼承的"父"協(xié)議中的函數(shù)
print("123456")
}
}
Tips:
上面提到的"如果一個(gè)協(xié)議繼承了基協(xié)議NSObjectProtocol,那么遵守這個(gè)協(xié)議的類也必須要繼承NSObject這個(gè)類"
這是因?yàn)槲覀冃枰狽SObject這個(gè)父類來幫我們實(shí)現(xiàn)NSObjectProtocol中定義的函數(shù),否則編譯器會(huì)以"沒有實(shí)現(xiàn)NSObjectProtocol中的函數(shù)為由而報(bào)錯(cuò)
協(xié)議中可選實(shí)現(xiàn)的函數(shù)
為了保證Swift語言的嚴(yán)謹(jǐn)性,不建議在協(xié)議中定義可選實(shí)現(xiàn)的函數(shù),不過不建議不代表不能嘛,我們可以利用OC特性來實(shí)現(xiàn)在Swift協(xié)議中定義可選實(shí)現(xiàn)函數(shù)
- 創(chuàng)建帶有OC特性的協(xié)議
@objc //表示一下代碼含有OC特性
protocol showTicketNumberProtocol {
optional func showTicketNumber() -> Void //optional修飾的函數(shù)為可選擇實(shí)現(xiàn)(或不實(shí)現(xiàn))的函數(shù)
}
- 遵守帶有OC特性的協(xié)議
class Tout : showTicketNumberProtocol {
//終于,下面這個(gè)函數(shù)可以不實(shí)現(xiàn),并且不會(huì)報(bào)錯(cuò)了
@objc func showTicketNumber() { //由于showTicketNumberProtocol含有OC特性,于是這個(gè)協(xié)議中所有的函數(shù)在實(shí)現(xiàn)之前都要有@objc來修飾
print("123456")
}
}