Swift協(xié)議與擴(kuò)展

轉(zhuǎn)載:http://www.lxweimin.com/p/e70bd6645d88
前言

熟悉Objective-C語(yǔ)言的同學(xué)們肯定對(duì)協(xié)議都不陌生,在Swift中蘋(píng)果將protocol這種語(yǔ)法發(fā)揚(yáng)的更加深入和徹底。Swift中的protocol不僅能定義方法還能定義屬性,配合extension擴(kuò)展的使用還能提供一些方法的默認(rèn)實(shí)現(xiàn),而且不僅類可以遵循協(xié)議,現(xiàn)在的枚舉和結(jié)構(gòu)體也能遵循協(xié)議了?;诖吮疚膹?1、協(xié)議中定義屬性和方法,2、協(xié)議的繼承、聚合、關(guān)聯(lián)類型,3、協(xié)議的擴(kuò)展,4、Swift標(biāo)準(zhǔn)庫(kù)中常見(jiàn)的協(xié)議,5、為什么要使用協(xié)議 5個(gè)方面結(jié)合自身的學(xué)習(xí)經(jīng)驗(yàn)簡(jiǎn)單介紹一下這種“加強(qiáng)型protocol”的使用,入門(mén)級(jí)、屬于學(xué)習(xí)總結(jié),希望能給正在學(xué)習(xí)Swift的小伙伴們一點(diǎn)啟發(fā)。

協(xié)議中定義屬性和方法

協(xié)議的定義
官方文檔對(duì)協(xié)議的定義是這樣的:

協(xié)議為方法、屬性、以及其他特定的任務(wù)需求或功能定義藍(lán)圖。協(xié)議可被類、結(jié)構(gòu)體、或枚舉類型采納以提供所需功能的具體實(shí)現(xiàn)。滿足了協(xié)議中需求的任意類型都叫做遵循了該協(xié)議。
Swift中定義一個(gè)協(xié)議和定義枚舉、結(jié)構(gòu)體或者類的格式類似,使用protocol關(guān)鍵字:

//定義一個(gè)名字為學(xué)生協(xié)議
protocol Student {
}
這里Student是使用protocol 關(guān)鍵字聲明的一個(gè)協(xié)議,和枚舉、結(jié)構(gòu)體、類命名原則相似,Student首字母大寫(xiě)表示在以后的使用中很可能會(huì)將Student看作是一個(gè)類型使用。

協(xié)議中定義屬性
協(xié)議中定義屬性表示遵循該協(xié)議的類型具備了某種屬性,具體來(lái)說(shuō)只能使用var關(guān)鍵字聲明并且必須明確規(guī)定該屬性是可讀的get、還是可讀可寫(xiě)的get set,另外還可以通過(guò)關(guān)鍵字static聲明一個(gè)類型屬性。示例如下:

protocol Student {
//定義一個(gè)可讀可寫(xiě)的 name 屬性
var name: String { get set }
//定義一個(gè)可讀的 birthPlace 屬性
var birthPlace: String { get }
//定義一個(gè)類屬性 record
static var qualification: String {get}
}
和定義方法一樣,我們只需要確定該屬性具體是什么類型并且添加對(duì)應(yīng)的關(guān)鍵字,不需要具體的實(shí)現(xiàn),更不能為他們賦上初始值(類似于計(jì)算屬性)。定義好屬性之后,我們就可以利用屬性來(lái)做點(diǎn)事情了。

struct Puple: Student {
static var qualification: String = "小學(xué)"
var name: String
var birthPlace: String = "北京"
}
var p1 = Puple(name: "小明", birthPlace: "上海")
定義一個(gè)Puple結(jié)構(gòu)體遵循Student協(xié)議,該結(jié)構(gòu)體中必須存在協(xié)議要求聲明的三個(gè)屬性matrikelnummer、name、birthPlace,static修飾的類型屬性必須被有初始值或者存在get、set方法。對(duì)于普通的實(shí)例屬性協(xié)議并不關(guān)心是計(jì)算型屬性還是存儲(chǔ)型屬性。實(shí)例中的屬性同樣可以被修改:

var p1 = Puple(name: "小明", birthPlace: "上海")
Puple.qualification = "中學(xué)"
看到這里有的同學(xué)可能有些疑問(wèn),birthPlace、qualification明明只有g(shù)et方法為什么卻可以修改賦值呢?其實(shí)協(xié)議中的“只讀”屬性修飾的是協(xié)議這種“類型”的實(shí)例,例如下面的例子:

var s1: Student = p1
s1.birthPlace = "廣州"
雖然我們并不能像創(chuàng)建類的實(shí)例那樣直接創(chuàng)建協(xié)議的實(shí)例,但是我們可以通過(guò)“賦值”得到一個(gè)協(xié)議的實(shí)例。將p1的值賦值給Student類型的變量s1,修改s1的birthPlace屬性時(shí)編譯器就會(huì)報(bào)錯(cuò):birthPlace是一個(gè)只讀的屬性,不能被修改。如果Puple中還存在Student沒(méi)有的屬性,那么在賦值過(guò)程中s1將不會(huì)存在這樣的屬性,盡管這樣做的意義并不大,但是我們從中知道了協(xié)議中g(shù)et、set的具體含義。

協(xié)議中定義方法
和Objective-C類似,Swift中的協(xié)議可以定義類型方法或者實(shí)例方法,方法的參數(shù)不能有默認(rèn)值(Swift認(rèn)為默認(rèn)值也是一種變相的實(shí)現(xiàn)),在遵守該協(xié)議的類型中具體實(shí)現(xiàn)方法的細(xì)節(jié),通過(guò)類或?qū)嵗{(diào)用:

protocol Student {
//類方法
static func study()
//實(shí)例方法
func changeName()
}
struct CollageStudent: Student {
//類方法實(shí)現(xiàn)
static func study() {
}
//實(shí)例方法實(shí)現(xiàn)
func changeName() {
}
}
//方法的調(diào)用
CollageStudent.study()
var c1 = CollageStudent()
c1.changeName()
注意:當(dāng)我們?cè)诮Y(jié)構(gòu)體中的方法修改到屬性的時(shí)候需要在方法前面加上關(guān)鍵字mutating表示該屬性能夠被修改(如果是類不需要添加mutating 關(guān)鍵字),這樣的方法叫做:異變方法,和 “在實(shí)例方法中修改值類型” 的處理是一樣的。

protocol Student {
mutating func changeName()
}
struct CollageStudent: Student {
mutating func changeName() {
self.name = "小明"
}
}
var c1 = CollageStudent()
c1.changeName()
協(xié)議中的初始化器
我們可以在協(xié)議中定義遵循協(xié)議的類型需要實(shí)現(xiàn)的指定初始化器(構(gòu)造函數(shù))或者便捷初始化器。

protocol Pet {
init(name: String)
}
class Cat: Pet {
var name: String = "Cat"
required init(name: String) {
self.name = name
}
}
Cat由于遵循了Pet協(xié)議,應(yīng)該用required關(guān)鍵字修飾初始化器的具體實(shí)現(xiàn)。
如果一個(gè)類既繼承了某個(gè)類,而且遵循了一個(gè)或多個(gè)協(xié)議,我們應(yīng)該將父類放在最前面,然后依次用逗號(hào)排列。

class SomeClass: OneProtocol, TwoProtocol {
}
這是因?yàn)镾wift中類的繼承是單一的,但是類可以遵守多個(gè)協(xié)議,因此為了突出其單一父類的特殊性,我們應(yīng)該將繼承的父類放在最前面,將遵守的協(xié)議依次放在后面。

多個(gè)協(xié)議重名方法調(diào)用沖突
由于在Swift中并沒(méi)有規(guī)定不同的協(xié)議內(nèi)方法不能重名(這樣的規(guī)定也是不合理的)。因此我們?cè)谧远x多個(gè)協(xié)議中方法重名的情況是可能出現(xiàn)的,比如存在TextOne、TextTwo兩個(gè)協(xié)議,定義如下:

protocol TextOne {
func text() -> Int
}
protocol TextTwo {
func text() -> String
}
這兩個(gè)協(xié)議中的text()方法名相同返回值不同,如果存在一個(gè)類型Person同時(shí)遵守了TextOne 和TextTwo,在Person實(shí)例調(diào)用方法的時(shí)候就會(huì)出現(xiàn)歧義。

struct Person: TextOne, TextTwo {
func text() -> Int {
return 10
}
func text() -> String {
return "hello"
}
}
let p1 = Person()
//嘗試調(diào)用返回值為Int的方法
let num = p1.text()
//嘗試調(diào)用返回值為String的方法
let string = p1.text()
上面的調(diào)用肯定是無(wú)法通過(guò)的,因?yàn)榫幾g器無(wú)法知道同名text()方法到底是哪個(gè)協(xié)議中的方法,那么出現(xiàn)這種情況的根本原因在于調(diào)用哪個(gè)協(xié)議的text()不確定,因此我們需要指定調(diào)用特定協(xié)議的text()方法,改進(jìn)后的代碼如下:

//嘗試調(diào)用返回值為Int的方法
let num = (p1 as TextOne).text()
//嘗試調(diào)用返回值為String的方法
let string = (p1 as TextTwo).text()
也可以理解為在進(jìn)行調(diào)用前將p1常量進(jìn)行類型轉(zhuǎn)換。

協(xié)議的繼承、聚合、關(guān)聯(lián)類型

協(xié)議的繼承
官方文檔說(shuō)明:

協(xié)議可以繼承一個(gè)或者多個(gè)其他協(xié)議并且可以在它繼承的基礎(chǔ)之上添加更多要求。協(xié)議繼承的語(yǔ)法與類繼承的語(yǔ)法相似,選擇列出多個(gè)繼承的協(xié)議,使用逗號(hào)分隔。
protocol OneProtocol {
}
protocol TwoProtocol {
}
//定義一個(gè)繼承子OneProtocol 和 TwoProtocol協(xié)議的新的協(xié)議: ThreeProtocol
protocol ThreeProtocol: OneProtocol, TwoProtocol {
}
如上所示,任何遵守了ThreeProtocol協(xié)議的類型都應(yīng)該同時(shí)實(shí)現(xiàn)OneProtocol 和 TwoProtocol的要求必須實(shí)現(xiàn)的方法或?qū)傩浴?/p>

協(xié)議的聚合
日常開(kāi)發(fā)中要求一個(gè)類型同時(shí)遵守多個(gè)協(xié)議是很常見(jiàn)的,除了使用協(xié)議的繼承外我們還可以使用形如OneProtocol & TwoProtocol的形式實(shí)現(xiàn)協(xié)議聚合(組合)復(fù)合多個(gè)協(xié)議到一個(gè)要求里。例如:

//協(xié)議聚合成臨時(shí)的類型
typealias Three = TwoProtocol & OneProtocol
//協(xié)議聚合成為參數(shù)的類型
func text(paramter: OneProtocol & TwoProtocol) {
}
一個(gè)很常見(jiàn)的例子:定義text函數(shù)的參數(shù)類型使用了協(xié)議的聚合,在這里我們并不關(guān)心paramter是什么類型的參數(shù),只要它遵循這兩個(gè)要求的協(xié)議即可。

繼承和聚合在使用上的區(qū)別
善于思考的同學(xué)可以發(fā)現(xiàn),要實(shí)現(xiàn)上面的 "paramter參數(shù)的類型是遵守OneProtocol 和 TwoProtoco" 的效果,完全可以使用協(xié)議的繼承,新定義一個(gè)協(xié)議ThreeProtocol繼承自O(shè)neProtocol 和TwoProtocol,然后指定paramter參數(shù)的類型是ThreeProtocol類型。那么這兩種方法有何區(qū)別呢?
首先協(xié)議的繼承是定義了一個(gè)全新的協(xié)議,我們是希望它能夠“大展拳腳”得到普遍使用。而協(xié)議的聚合不一樣,它并沒(méi)有定義新的固定協(xié)議類型,相反,它只是定義一個(gè)臨時(shí)的擁有所有聚合中協(xié)議要求組成的局部協(xié)議,很可能是“一次性需求”,使用協(xié)議的聚合保持了代碼的簡(jiǎn)潔性、易讀性,同時(shí)去除了定義不必要的新類型的繁瑣,并且定義和使用的地方如此接近,見(jiàn)明知意,也被稱為匿名協(xié)議聚合。但是使用了匿名協(xié)議聚合能夠表達(dá)的信息就少了一些,所以需要開(kāi)發(fā)者斟酌使用。

協(xié)議的檢查
如何檢查某個(gè)類型是否遵循了特定的協(xié)議?:使用關(guān)鍵字 is,同時(shí)該運(yùn)算符會(huì)返回一個(gè)Bool值用于判斷條件是否成立。

struct Person: OneProtocol {
}
let p1 = Person()
if (p1 is OneProtocol){ //可以理解為:p1 是一個(gè)遵守了OneProtocol協(xié)議類型的實(shí)例
print("yes")
}
如何讓定義的協(xié)議只能被類遵守?:使用關(guān)鍵字class,該關(guān)鍵字修飾之后表示協(xié)議只能被類遵守,如果有枚舉或結(jié)構(gòu)體嘗試遵守會(huì)報(bào)錯(cuò)。

//只能被類遵守的協(xié)議
protocol FourProtocol: class ,ThreeProtocol {
}
//此處報(bào)錯(cuò)
struct Person: FourProtocol {
}
class Perple: FourProtocol {
}
關(guān)聯(lián)類型
協(xié)議的關(guān)聯(lián)類型指的是根據(jù)使用場(chǎng)景的變化,如果協(xié)議中某些屬性存在“邏輯相同的而類型不同”的情況,可以使用關(guān)鍵字associatedtype來(lái)為這些屬性的類型聲明“關(guān)聯(lián)類型”。

protocol WeightCalculable {
//為weight 屬性定義的類型別名
associatedtype WeightType
var weight: WeightType { get }
}
WeightCalculable是一個(gè)“可稱重”協(xié)議,weight屬性返回遵守該協(xié)議具體類型的實(shí)例的重量。這里我們使用associatedtype為該屬性的類型定義了一個(gè)別名WeightType,換言之在WeightCalculable中并不關(guān)心weight的類型是Int 還是Double或者是其他類型,他只是簡(jiǎn)單的告訴我們返回的類型是WeightType,至于WeightType到底是什么類型由遵守該協(xié)議的類中自己去定義。那么這樣做的好處是什么呢?

//定義手機(jī)結(jié)構(gòu)體
struct MobilePhone: WeightCalculable {
typealias WeightType = Double
var weight: WeightType
}
let iPhone7 = MobilePhone(weight: 0.138)
//定義汽車結(jié)構(gòu)體
struct Car: WeightCalculable {
typealias WeightType = Int
var weight: WeightType
}
let truck = Car(weight: 3000_000)
如上所示:MobilePhone、Car類型都遵守了WeightCalculable協(xié)議,都能被稱重,但是手機(jī)由于結(jié)構(gòu)精密、體型小巧,小數(shù)點(diǎn)后面的數(shù)字對(duì)于稱重來(lái)說(shuō)是必不可少的,所以使用了Double類型,返回0.138千克即138克,但是對(duì)于汽車這樣的龐然大物在稱重時(shí)如果還計(jì)較小數(shù)點(diǎn)后面的數(shù)字就顯得沒(méi)有意義了,所以使用Int類型,表示3000千克也就是3噸。
從上面的例子可以很好的看出由于MobilePhone、Car稱重時(shí)邏輯是一樣的,但是對(duì)于weight屬性的返回值要求不一樣,如果僅僅因?yàn)榉祷刂殿愋偷牟煌x兩個(gè)類似的協(xié)議一個(gè)是Int類型另外一個(gè)是Double類型,這樣做顯然是重復(fù)的、不合適的。所以associatedtype在這種情況下就發(fā)揮出作用了,他讓開(kāi)發(fā)者在遵守協(xié)議時(shí)根據(jù)需求去定義返回值的類型,而不是在協(xié)議中寫(xiě)死。唯一要注意的是:一定要在遵守該協(xié)議的類型中使用typealias規(guī)定具體的類型。不然編譯器就報(bào)錯(cuò)了。

協(xié)議的擴(kuò)展

協(xié)議的擴(kuò)展是協(xié)議中很重要的一部分內(nèi)容,主要體現(xiàn)在以下兩個(gè)方面:

擴(kuò)展協(xié)議的屬性和方法
我們通過(guò)一個(gè)常見(jiàn)的例子說(shuō)明一下:

protocol Score {
var math: Int { get set}
var english: Int {get set}
func mathPercent() -> Double
}
首先定義一個(gè)Score協(xié)議,里面有兩個(gè)Int類型的屬性math、english和一個(gè)計(jì)算數(shù)學(xué)所占分?jǐn)?shù)的比例的方法mathPercent。

struct Puple: Score {
var math: Int
var english: Int
func mathPercent() -> Double {
return Double(math) / Double(math + english)
}
}
定義Puple遵守該協(xié)議,實(shí)現(xiàn)了必要的屬性和方法。

let p1 = Puple(math: 90, english: 80)
s1.mathPercent()
通過(guò)上面的代碼可以計(jì)算出s1中數(shù)學(xué)所占的比例,但是設(shè)想一下如果還有很多個(gè)類似Puple結(jié)構(gòu)體的類型都需要遵守該協(xié)議,都需要默認(rèn)實(shí)現(xiàn)mathPercent 方法計(jì)算出自己的數(shù)學(xué)分?jǐn)?shù)所占的比例,還是按照上面的寫(xiě)法代碼量就很大而且很冗雜了。問(wèn)題的關(guān)鍵在于:任何遵守Score協(xié)議類型的mathPercent計(jì)算邏輯是不變的,而且需要默認(rèn)實(shí)現(xiàn)。那么我們?nèi)绾屋p松的實(shí)現(xiàn)這樣的效果呢,答案是:為Score添加方法的擴(kuò)展。

extension Score {
func mathPercent() -> Double {
return Double(math) / Double(math + english)
}
}
將mathPercent的具體實(shí)現(xiàn)寫(xiě)在協(xié)議的擴(kuò)展里面,就能為所有的遵守Score的類型提供mathPercent默認(rèn)的實(shí)現(xiàn)。

struct CollageStudent: Score {
var math: Int
var english: Int
}
let c1 = CollageStudent(math: 80, english: 80)
c1.mathPercent()
如此就能起到“不實(shí)現(xiàn)mathPercent方法也能計(jì)算出數(shù)學(xué)所占分?jǐn)?shù)的比例”的效果了。此語(yǔ)法在Swift中有一個(gè)專業(yè)術(shù)語(yǔ)叫做:default implementation 即默認(rèn)實(shí)現(xiàn)。包括計(jì)算屬性和方法的默認(rèn)實(shí)現(xiàn),但是不支持存儲(chǔ)屬性,如果遵循類型給這個(gè)協(xié)議的要求提供了它自己的實(shí)現(xiàn),那么它就會(huì)替代擴(kuò)展中提供的默認(rèn)實(shí)現(xiàn)。
通過(guò)這樣的語(yǔ)法,我們不僅能為自定義的協(xié)議提供擴(kuò)展,還能為系統(tǒng)提供的協(xié)議添加擴(kuò)展,例如,為CustomStringConvertible添加一個(gè)計(jì)算屬性默認(rèn)實(shí)現(xiàn)的擴(kuò)展:

extension CustomStringConvertible {
var customDescription: String {
return "YQ" + description
}
}
為存在的類型添加協(xié)議遵守
官方文檔說(shuō)明:

擴(kuò)展一個(gè)已經(jīng)存在的類型來(lái)采納和遵循一個(gè)新的協(xié)議,無(wú)需訪問(wèn)現(xiàn)有類型的源代碼。擴(kuò)展可以添加新的屬性、方法和下標(biāo)到已經(jīng)存在的類型,并且因此允許你添加協(xié)議需要的任何需要。
簡(jiǎn)單的來(lái)說(shuō)我們可以對(duì)存在的類型(尤其是系統(tǒng)的類型)添加協(xié)議遵守。盡管這更像是對(duì)“類型的擴(kuò)展”,但是官方文檔將這部分放在了協(xié)議的擴(kuò)展中。

extension Double : CustomStringConvertible {
/// A textual representation of the value.
public var description: String { get }
}
上面的代碼就是Swift標(biāo)準(zhǔn)庫(kù)中對(duì)于Double類型添加的一個(gè)協(xié)議遵守。除了添加系統(tǒng)協(xié)議的遵守,我們還可以添加自定義的協(xié)議的遵守,其方法都是一樣的,這里就不太贅述了。

總結(jié)
通過(guò)協(xié)議的擴(kuò)展提供協(xié)議中某些屬性和方法的默認(rèn)實(shí)現(xiàn),將公共的代碼和屬性統(tǒng)一起來(lái)極大的增加了代碼的復(fù)用,同時(shí)也增加了協(xié)議的靈活性和使用范圍,這樣的協(xié)議不僅僅是一系列接口的規(guī)范,還能提供相應(yīng)的邏輯,是面向協(xié)議編程的基礎(chǔ)。

Swift標(biāo)準(zhǔn)庫(kù)中常見(jiàn)的協(xié)議

學(xué)習(xí)完協(xié)議的基礎(chǔ)語(yǔ)法,我們大致熟悉一下Swift標(biāo)準(zhǔn)庫(kù)中提供的協(xié)議。

![Uploading 2474121-48373a5702d649ef_956494.png . . .]

55個(gè)標(biāo)準(zhǔn)庫(kù)協(xié)議
Swift標(biāo)準(zhǔn)庫(kù)為我們提供了55種協(xié)議,他們的命名很有特點(diǎn),基本是以"Type"、“able”、“Convertible” 結(jié)尾,分別表示該協(xié)議“可以被當(dāng)做XX類型”、“具備某種能力或特性”、“能夠進(jìn)行改變或變換”。因此在自定義協(xié)議時(shí)應(yīng)該盡可能遵守蘋(píng)果的命名規(guī)則,便于開(kāi)發(fā)人員之間的高效合作。下面介紹一下常見(jiàn)的幾種協(xié)議:

Equatable
Equatable是和比較相關(guān)的協(xié)議,遵守該協(xié)議表示實(shí)例能夠用于相等的比較,需要重載==運(yùn)算符。
struct Student: Equatable {
var math: Int
var english: Int
}
//重載 == 運(yùn)算符
func == (s1: Student, s2: Student) -> Bool {
return s1.math == s2.math && s1.english == s2.english
}
Student遵守Equatable并且重載了==運(yùn)算符后就能直接比較兩個(gè)學(xué)生的成績(jī)是否相等了。
let s1 = Student(math: 80, english: 60)
let s2 = Student(math: 70, english: 90)
s1 == s2 //false
值得注意的是,由于重載==運(yùn)算符是遵守Equatable協(xié)議后要求我們實(shí)現(xiàn)的,因此重載方法應(yīng)該緊跟在遵守該協(xié)議的類型定義后,中間不能有其他的代碼,否則就報(bào)錯(cuò)了。
Comparable
Comparable是和比較相關(guān)的第二個(gè)協(xié)議,遵守該協(xié)議表示實(shí)例能夠進(jìn)行比較,需要重載<運(yùn)算符。
struct Student: Comparable{
var math: Int
var english: Int
}
//重載 < 運(yùn)算符
func < (s1: Student, s2: Student) -> Bool {
return (s1.math + s1.english) < (s2.math + s2.english)
}
let s1 = Student(math: 80, english: 60)
let s2 = Student(math: 70, english: 90)
s1 < s2 //true
CustomStringConvertible
CustomStringConvertible提供了一種用文本表示一個(gè)對(duì)象或者結(jié)構(gòu)的方式,可以在任何遵守該協(xié)議的類型中自定義表示結(jié)構(gòu)的文本,需要覆蓋description屬性。
struct Student: CustomStringConvertible{
var math: Int
var english: Int
var description: String {
return "Your math:" + String(math) + ", english:" + String(english)
}
}
let s1 = Student(math: 80, english: 60)
print(s1) // Your math:80, english:60
ExpressibleByArrayLiteral
ExpressibleByArrayLiteral提供了使用數(shù)組文本初始化的類型的能力,具體來(lái)說(shuō)使用逗號(hào)分隔的值、實(shí)例、字面值列表,方括號(hào)以創(chuàng)建數(shù)組文本。遵守該協(xié)議需要實(shí)現(xiàn)init(arrayLiteral elements: Person.Element...)方法。
struct Person: ExpressibleByArrayLiteral {
var name: String = ""
var job: String = ""
typealias Element = String
init(arrayLiteral elements: Person.Element...) {
if elements.count == 2 {
name = elements[0]
job = elements[1]
}
}
}
let p1: Person = ["jack", "teacher"]
print(p1.name) //jack
print(p1.job) //teacher
上面的代碼用到了之前關(guān)聯(lián)類型,通過(guò)遵守ExpressibleByArrayLiteral,現(xiàn)在的Person就可以使用數(shù)組直接創(chuàng)建實(shí)例了。
類似的協(xié)議還有:
ExpressibleByDictionaryLiteral、ExpressibleByStringLiteral、ExpressibleByBooleanLiteral 、ExpressibleByIntegerLiteral等等,相信大家通過(guò)名稱就能大概猜出具體作用,由于篇幅有限這里就不再贅述了。

為什么要使用協(xié)議

協(xié)議可以作為類型使用
協(xié)議作為一種類型是蘋(píng)果在Swift中提出的,并且在官方文檔中還為我們具體指出了可以將協(xié)議當(dāng)做類型使用的場(chǎng)景:
1,在函數(shù)、方法或者初始化器里作為形式參數(shù)類型或者返回類型;
2,作為常量、變量或者屬性的類型;
3,作為數(shù)組、字典或者其他存儲(chǔ)器的元素的類型。
協(xié)議可以解決面向?qū)ο笾幸恍┘值膯?wèn)題

2474121-48373a5702d649ef.png

寵物類圖

如圖所示的類結(jié)構(gòu)圖中麻雀在寵物類圖中的位置顯得比較尷尬,之所以尷尬是因?yàn)槁槿缸鳛橐环N鳥(niǎo)類,應(yīng)該繼承鳥(niǎo),但是如果繼承了鳥(niǎo),就相當(dāng)于默認(rèn)了麻雀是一種寵物,這顯然是不和邏輯的。解決此問(wèn)題的一般方法如下:

2474121-645062500b1cd422.png

寵物類圖

乍一看好像解決了這樣的問(wèn)題,但是仔細(xì)想由于Swift只支持單繼承,麻雀沒(méi)有繼承鳥(niǎo)類就無(wú)法體現(xiàn)麻雀作為一種鳥(niǎo)擁有的特性和方法(比如飛翔),如果此時(shí)出現(xiàn)一個(gè)新的飛機(jī)類,雖然飛機(jī)和寵物之間沒(méi)有任何聯(lián)系,但是飛機(jī)和鳥(niǎo)是由很多共同特性的(比如飛翔),這樣的特性該如何體現(xiàn)呢?答案還是新建一個(gè)類成為動(dòng)物和飛機(jī)的父類。面向?qū)ο缶褪沁@樣一層一層的向上新建父類最終得到一個(gè)“超級(jí)父類”在OC和Swift中是NSObject,盡管問(wèn)題得到了解決,但是麻雀與鳥(niǎo)、飛機(jī)與鳥(niǎo)之間的共性并沒(méi)有得到很好的體現(xiàn)。而協(xié)議的出現(xiàn)正是為了解決這類問(wèn)題。

2474121-f3fb7def9e9be687.png

寵物類圖

事實(shí)上寵物類圖中包括動(dòng)物、鳥(niǎo)、飛機(jī)等類之間的關(guān)系就應(yīng)該是如上圖所示的簡(jiǎn)單繼承關(guān)系。使用協(xié)議將“寵物”、“飛翔”等關(guān)系看作是一種特性,或者是從另一個(gè)維度描述這種類別,更重要的是使用協(xié)議并不會(huì)打破原有類別之間繼承的父子關(guān)系。和飛翔相關(guān)的代碼統(tǒng)一放在Flyable中,需要“飛翔”這種能力遵守該協(xié)議;和寵物相關(guān)的代碼統(tǒng)一放在PetType中,需要成為寵物遵守該協(xié)議。這些協(xié)議靈活多變結(jié)合原有的面向?qū)ο箢愔g固有的繼承關(guān)系,完美的描述了這個(gè)世界。這幅包含了協(xié)議的寵物類圖是本人在學(xué)習(xí)中印象最深刻的,分享出來(lái)與大家共勉。
Swift中的協(xié)議更多的時(shí)候是在描述某種屬性,是否應(yīng)該將“寵物”設(shè)計(jì)成一個(gè)類或者是一個(gè)協(xié)議,應(yīng)該根據(jù)當(dāng)前項(xiàng)目的需求。如果你的世界沒(méi)有麻雀、飛機(jī),那么將“寵物”設(shè)計(jì)成一個(gè)類也是未嘗不可甚至是非常合理的,這點(diǎn)需要我們仔細(xì)思考。

面向協(xié)議編程范式
學(xué)習(xí)使用協(xié)議就不得不提到通過(guò)協(xié)議語(yǔ)法延伸出來(lái)的 “面向協(xié)議編程范式”,蘋(píng)果提出Swift是一門(mén)支持面向協(xié)議編程的語(yǔ)言,甚至提倡我們通過(guò)協(xié)議、結(jié)構(gòu)體代替部分類的使用,可見(jiàn)協(xié)議這種語(yǔ)法以及面向協(xié)議編程思想在Swift中是多么的重要。在這里由于筆者水平有限就不對(duì)此展開(kāi)討論,不過(guò)在學(xué)習(xí)中收集了幾篇關(guān)于“使用協(xié)議編程”方面的文章,有興趣的同學(xué)可以參考一下。
1,從 Swift 的面向協(xié)議編程說(shuō)開(kāi)去
2,我從55個(gè)Swift標(biāo)準(zhǔn)庫(kù)協(xié)議中學(xué)到了什么?
3,Swift面向協(xié)議編程初探

文章最后

以上就是本人關(guān)于協(xié)議語(yǔ)法的心得,示例代碼在Swift3.0語(yǔ)法下都是編譯通過(guò)的,知識(shí)點(diǎn)比較雜,部分描述引自官方的文檔,另外協(xié)議作為一種強(qiáng)大的語(yǔ)法肯定還有很多值得我們?nèi)ヌ剿鳎疚牧谐龅闹R(shí)點(diǎn)僅僅涵括了部分內(nèi)容。如果文中有任何紕漏或錯(cuò)誤歡迎在評(píng)論區(qū)留言指出,本人將在第一時(shí)間修改過(guò)來(lái);喜歡我的文章,可以關(guān)注我以此促進(jìn)交流學(xué)習(xí); 如果覺(jué)得此文戳中了你的G點(diǎn)請(qǐng)隨手點(diǎn)贊;轉(zhuǎn)載請(qǐng)注明出處,謝謝支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容