轉(zhuǎn)載:http://www.reibang.com/p/e70bd6645d88
前言
熟悉Objective-C語(yǔ)言的同學(xué)們肯定對(duì)協(xié)議都不陌生,在Swift中蘋果將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”的使用由桌,入門級(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首字母大寫表示在以后的使用中很可能會(huì)將Student看作是一個(gè)類型使用。
協(xié)議中定義屬性
協(xié)議中定義屬性表示遵循該協(xié)議的類型具備了某種屬性队贱,具體來(lái)說(shuō)只能使用var關(guān)鍵字聲明并且必須明確規(guī)定該屬性是可讀的get色冀、還是可讀可寫的get set,另外還可以通過(guò)關(guān)鍵字static聲明一個(gè)類型屬性柱嫌。示例如下:
protocol Student {
//定義一個(gè)可讀可寫的 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é)議的類型中使用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ù)所占的比例,還是按照上面的寫法代碼量就很大而且很冗雜了陈莽。問(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é)議的擴(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é)議池摧。
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)該盡可能遵守蘋果的命名規(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é)議作為一種類型是蘋果在Swift中提出的甲捏,并且在官方文檔中還為我們具體指出了可以將協(xié)議當(dāng)做類型使用的場(chǎng)景:
1,在函數(shù)鞭执、方法或者初始化器里作為形式參數(shù)類型或者返回類型司顿;
2芒粹,作為常量、變量或者屬性的類型大溜;
3化漆,作為數(shù)組、字典或者其他存儲(chǔ)器的元素的類型钦奋。
協(xié)議可以解決面向?qū)ο笾幸恍┘值膯?wèn)題
寵物類圖
如圖所示的類結(jié)構(gòu)圖中麻雀在寵物類圖中的位置顯得比較尷尬座云,之所以尷尬是因?yàn)槁槿缸鳛橐环N鳥(niǎo)類,應(yīng)該繼承鳥(niǎo)付材,但是如果繼承了鳥(niǎo)朦拖,就相當(dāng)于默認(rèn)了麻雀是一種寵物,這顯然是不和邏輯的厌衔。解決此問(wèn)題的一般方法如下:
寵物類圖
乍一看好像解決了這樣的問(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)題赃梧。
寵物類圖
事實(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é)議編程范式”,蘋果提出Swift是一門支持面向協(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)注明出處盏求,謝謝支持抖锥。