本篇文章翻譯自:Introducing Protocol-Oriented Programming in Swift 2)
原作:[Erik kerber]
(https://www.raywenderlich.com/u/kerber) on June 25, 2015
注: 這篇tutorial需要XCode 7, Swift2, 這個(gè)秋天會(huì)發(fā)布測(cè)試版病蛉。你也可以在 Apple's developer portal下載最新版本。
WWDC 2015, 蘋果發(fā)布了swift 2,宣布了swift語(yǔ)言第二次重大修訂铺然,其中包括了幾個(gè)新的語(yǔ)言特征俗孝,以幫助你提升編碼方式。
在這些的新特征中魄健,最令人興奮的是協(xié)議擴(kuò)展赋铝。在swift第一個(gè)版本,你可以擴(kuò)展已經(jīng)存在的類沽瘦,結(jié)構(gòu)體革骨,和枚舉類型的功能。現(xiàn)在有了swift 2析恋,你也可以擴(kuò)展協(xié)議良哲。
可能剛開始你會(huì)覺得這是一個(gè)微不足道的特征,但是協(xié)議擴(kuò)展真的能量巨大助隧,且可以轉(zhuǎn)變你的編碼方式筑凫。在這篇turorial,你會(huì)探索創(chuàng)建,使用協(xié)議擴(kuò)展的方法并村,還有新技術(shù)和面向協(xié)議編程模式(以下簡(jiǎn)稱:POP)巍实。
你將會(huì)看到Swift團(tuán)隊(duì)是怎樣使用協(xié)議擴(kuò)展以提升swift標(biāo)準(zhǔn)庫(kù),和協(xié)議擴(kuò)展怎樣影響你的代碼哩牍。
開始
開始在XCode中創(chuàng)建一個(gè)新的Playground, 選擇File\New\Playground...蔫浆,然后命名Playground為SwiftProtocols。你可以選擇任何平臺(tái)姐叁,因?yàn)檫@篇tutorial所有的代碼跟平臺(tái)無關(guān)瓦盛。點(diǎn)擊Next選擇你要保存的路徑,然后點(diǎn)擊Create.
Playground打開后外潜,我們添加如下代碼:
protocol Bird {
var name: String { get }
var canFly:Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
}
這里簡(jiǎn)單的定義了一個(gè)擁有2個(gè)屬性name, 和canFly的Bird協(xié)議類型原环,還有一個(gè)Flyable類型,它定義了airspeedVelocity屬性处窥。
在pre-protocol時(shí)期嘱吗,你可能會(huì)把Flyable當(dāng)做基類,然后依賴?yán)^承去定義Bird還有其他能fly的東西滔驾,例如:灰機(jī)谒麦。請(qǐng)記住:一切始于協(xié)議哆致!
定義遵守協(xié)議的類型
在Playground下方添加struct的定義:
struct FlappyBird: Bird, Flyable {
let name: String
let flappyAmplitude: Double
let flappyFrequency: Double
let canFly = true
var airspeedVelocity: Double {
return 3 * flappyFrequency * flappyAmplitude
}
}
這里定義了一個(gè)新的結(jié)構(gòu)體FlappyBird, 它遵守Bird和Flyable協(xié)議绕德,airspeedVelocity為計(jì)算屬性。canFly屬性設(shè)置為true摊阀。
接下來耻蛇,在playground下面添加以下2個(gè)結(jié)構(gòu)體的定義:
struct Penguin: Bird {
let name: String
let canFly: Bool
}
struct SwiftBird: Bird, Flyable {
var name: String {
return "swift \(version)"
}
let version: Double
let canFly = true
var airspeedVelocity: Double {
return 2000.0
}
}
企鵝是鳥類踪蹬,但不會(huì)飛。哈--- 還好你沒有使用繼承臣咖,讓鳥類都能飛跃捣。海燕當(dāng)然有非常快的飛行速度夺蛇。
你可能已經(jīng)看到一些冗余疚漆。盡管在你的結(jié)構(gòu)中已經(jīng)有了Flayable的概念,每一種Bird類型還必須聲明是否canFly刁赦。
用默認(rèn)實(shí)現(xiàn)擴(kuò)展協(xié)議
使用協(xié)議擴(kuò)展娶聘,你可以為協(xié)議定義默認(rèn)行為。在Bird協(xié)議下添加如下代碼:
extension Bird where Self: Flyable {
var canFly: Bool { return true }
}
這里定義Bird的一個(gè)擴(kuò)展截型,當(dāng)類型也為Flyable時(shí),設(shè)置為canFly設(shè)置默認(rèn)行為true儒溉。換句話說宦焦,任何一個(gè)遵守Flyable的Bird類型不必在顯示聲明。
swift 1.2 引入了where語(yǔ)法顿涣,之后swift 2使用where語(yǔ)法給協(xié)議擴(kuò)展添加約束(Self作用是指波闹,當(dāng)Bird類型同時(shí)也遵守Flyable協(xié)議,canFly屬性才能生效)
現(xiàn)在從FlappyBird和SwiftBird結(jié)構(gòu)體聲明中刪除let canFly = true涛碑。你會(huì)發(fā)現(xiàn)playground成功編譯了精堕,因?yàn)閰f(xié)議擴(kuò)展幫你處理了協(xié)議的要求。
為什么不用基類
協(xié)議擴(kuò)展和其默認(rèn)實(shí)現(xiàn)好像跟其他語(yǔ)言的基類或者抽象類很相似蒲障,但是swift具有一下幾個(gè)關(guān)鍵優(yōu)勢(shì):
- 因?yàn)轭愋涂梢宰袷囟鄠€(gè)協(xié)議歹篓,可以擁有多個(gè)協(xié)議默認(rèn)實(shí)現(xiàn)的功能。不像其他編程語(yǔ)言支持的類的多繼承揉阎,協(xié)議擴(kuò)展不會(huì)引入額外的狀態(tài)庄撮。
- 協(xié)議可以被類,結(jié)構(gòu)體和枚舉類型遵守毙籽《此梗基類和繼承受限于類類型(class類型)
換句話說,協(xié)議擴(kuò)展為值類型坑赡,而不僅僅是為類類型提供了定義默認(rèn)行為的能力烙如。
你已經(jīng)在結(jié)構(gòu)體中見識(shí)過這種行為;接下來在playground添加枚舉定義:
enum UnladenSwallow: Bird, Flyable {
case Africancase
case Europeancase
case Unknown
var name: String {
switch self {
case .African:
return "African"
case .European:
return "European"
case .Unknown:
return "What do you mean? African or European?"
}
}
var airspeedVelocity: Double {
switch self {
case .African:
return 10.0
case .European:
return 9.9
case .Unknown:
fatalError("You are thrown from the bridge of death!")}
}
}
像其他值類型一樣毅否,遵守Bird和Flyable協(xié)議亚铁,你只需要定義協(xié)議要求的屬性。因?yàn)樗袷剡@2個(gè)協(xié)議螟加,所以UnladenSwallow的canFly屬性也獲得了默認(rèn)實(shí)現(xiàn)刀闷。
你真的認(rèn)為該 tutorial只會(huì)包括airspeedVelocity屬性熊泵,而不會(huì)包括Monty Python的引用。(哈甸昏,可擴(kuò)展顽分。。施蜜。)
擴(kuò)展協(xié)議
可能協(xié)議擴(kuò)展最普遍的用法就是擴(kuò)展外部協(xié)議了卒蘸,可能是定義在swift標(biāo)準(zhǔn)庫(kù)的協(xié)議,也可能是定義在第三方框架的協(xié)議翻默。在playground下面添加如下代碼:
extension CollectionType {
func skip(skip: Int) -> [Generator.Element] {
guard skip != 0 else { return [] }
var index = self.startIndex
var result: [Generator.Element] = []
var i = 0
repeat {
if i % skip == 0 {
result.append(self[index])
}
index = index.successor()
i += 1
}while ( index != self.endIndex)
return result
}
}
在CollectionType類型上擴(kuò)展了一個(gè)新的方法skip(:), 它會(huì)遍歷集合中的每一個(gè)元素缸沃,過濾掉不符合要求的元素(留下索引值能被參數(shù)skip整除對(duì)應(yīng)的元素),然后返回一個(gè)過濾后的數(shù)組修械。
CollectionType是一個(gè)被諸如swift中數(shù)組趾牧,字典等類型遵守的協(xié)議。這就意味著這個(gè)新的行為(skip(:) 方法)存在你APP中所有的CollectionType類型中肯污。playground中添加如下代碼,我們來見證這一行為:
let bunchBirds: [Bird] = [
UnladenSwallow.African,
UnladenSwallow.European,
UnladenSwallow.Unknown,
Penguin(name: "King Penguin", canFly: false),
SwiftBird(version: 2.0),
FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)
]
bunchBirds.skip(3)
這里哄芜,你定義了一個(gè)包含各種類型鳥類的數(shù)組柬唯,這些類型上文中你已經(jīng)定義過认臊。因?yàn)閿?shù)組遵守CollectionType, 那么它就能用skip(_:)方法。
擴(kuò)展你自己的協(xié)議
也許讓你興奮的是你可以像給swift標(biāo)準(zhǔn)庫(kù)添加新的行為一樣锄奢,你也可以給自己的協(xié)議定義新的行為。
修改Bird協(xié)議聲明师坎,使之遵守BooleanType協(xié)議:
protocol Bird: BooleanType {
var name: String { get }
var canFly: Bool { get }
}
遵守BooleanType協(xié)議意味著你的類型需要有一個(gè)Bool類型的 boolValue屬性。那么是不是意味著你必須給現(xiàn)在或者之后的每一個(gè)Bird類型都添加這么個(gè)屬性呢堪滨?
當(dāng)然胯陋,我們有更容易的辦法———協(xié)議擴(kuò)展。在Bird定義下添加如下代碼:
extension BooleanType where Self: Bird {
var boolValue: Bool {return self.canFly}
}
這個(gè)擴(kuò)展讓canFly屬性代表了每一個(gè)Bird類型的Boolean 值遏乔。playground中添加如下代碼发笔,一起來見證奇跡:
if UnladenSwallow.African {
print("I can fly!")
}else {
print("Guess I'll just sit here")
}
你會(huì)看到"I can fly!" 出現(xiàn)在輔助編輯器上。但是這里你僅是在if 語(yǔ)句中使用了UnladenSwallow捻激。(你可以挖掘更多用法!0贰!)
對(duì)swift標(biāo)準(zhǔn)庫(kù)的影響
你已經(jīng)見證過了協(xié)議擴(kuò)展是多么棒--- 你可以使用它自定義和擴(kuò)展你自己的代碼的能力调俘,還有你APP之外的協(xié)議(標(biāo)準(zhǔn)庫(kù)和第三方框架協(xié)議)旺垒。也許你會(huì)好奇,swift團(tuán)隊(duì)是怎么使用協(xié)議擴(kuò)展來提升swift標(biāo)準(zhǔn)庫(kù)的骇钦。
swift通過向標(biāo)準(zhǔn)庫(kù)中添加諸如map, reduce和filter等高階函數(shù)來提升函數(shù)式編程范式竞漾。這些方法存在不同CollectionType類型中,例如:Array:
["frog", "pants"].map{ $0.length}.reduce(0){$0 + $1}
array調(diào)用map會(huì)返回另一個(gè)array, 新的array調(diào)用reduce來歸納結(jié)果為9.(map會(huì)遍歷數(shù)組中每一個(gè)元素坦仍,并且返回由數(shù)組長(zhǎng)度組成的數(shù)組叨襟,reduce把新數(shù)組中元素長(zhǎng)度做加運(yùn)算)
這種情況下幔荒,map和reduce作為swift標(biāo)準(zhǔn)庫(kù)的一部分被包含在Array中,如果你按下cmd,點(diǎn)擊map右犹,你可以看到它怎么定義的:
extension Array : _ArrayType {
func map(transform: (T) -> U) -> [U]
}
這里的map函數(shù)被定義為Array的一個(gè)擴(kuò)展. swift的函數(shù)范式工作范圍遠(yuǎn)不止Array, 它在任意的CollectionType類型上都能工作姚垃。所以swift 1.2下是怎么工作的呢?
按下cmd,點(diǎn)擊Range的map函數(shù)掂墓,你將看到以下定義:
extension Range {
func map(transform: (T) -> U) -> [U]
}
這些表明在swift 1.2中看成, swift標(biāo)準(zhǔn)庫(kù)中的各種CollectionType類型需要重新定義map函數(shù)。因?yàn)楸M管Array和Range都是CollectionType吃嘿,但是結(jié)構(gòu)體不能被子類化,也沒有統(tǒng)一的實(shí)現(xiàn)亮瓷。
這并不僅僅是swift標(biāo)準(zhǔn)庫(kù)做出的細(xì)微差別贪嫂,也對(duì)你使用swift類型做出約束。
下面的泛型函數(shù)接受一個(gè)遵守Flyable協(xié)議的CollectionType類型參數(shù)斗塘,返回airspeedVelocity最大的元素亮靴。
func topSpeed(collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}
在swift 1.2中沒有協(xié)議擴(kuò)展,這會(huì)出現(xiàn)編譯錯(cuò)誤贞岭。map和reduce函數(shù)只會(huì)存在于預(yù)先定義的類型中搓侄,不會(huì)在任意的CollectionType都會(huì)工作讶踪。
在swift 2.0中有了協(xié)議擴(kuò)展,Array和Range的map定義如下:
extension CollectionType {
func map(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}
盡管你不能看到map源碼---至少到swift 2開源(目前已經(jīng)開源了)柱查,ColletionType有map的一個(gè)默認(rèn)實(shí)現(xiàn)云石,而且所有的CollectionType都能免費(fèi)獲得??汹忠。
在playground下面添加早先提到的泛型函數(shù):
func topSpeed(c: T) -> Double {
return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
map和reduce函數(shù)可以用在你的Flyable實(shí)例集合中了∫ド牛現(xiàn)在你終于可以回答他們中誰(shuí)是最快的参歹。
let flyingBirds: [Flyable] =[
UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version: 2.0)
]
topSpeed(flyingBirds) // 2000.
結(jié)果應(yīng)該無人質(zhì)疑吧??(海燕最快)
延伸閱讀
你可以在這里下載本tutorial完整playground代碼犬庇。
你已經(jīng)見識(shí)過POP的威力了臭挽,創(chuàng)建你自己的簡(jiǎn)單協(xié)議欢峰,然后使用協(xié)議擴(kuò)展來擴(kuò)展他們纽帖。有了默認(rèn)實(shí)現(xiàn)懊直,你可以給已經(jīng)存在的協(xié)議一個(gè)統(tǒng)一的實(shí)現(xiàn)室囊,有些像基類但是優(yōu)于基類盼铁,因?yàn)樗€可以應(yīng)用自愛結(jié)構(gòu)體和枚舉類型上饶火。
另外趁窃,協(xié)議擴(kuò)展不僅能夠勇子啊你自定義的協(xié)議上,還可以擴(kuò)展swift標(biāo)準(zhǔn)庫(kù)裆针,Cocoa, CocoaTouch中協(xié)議的默認(rèn)行為世吨。
如果想要瀏覽swift 2.0令人興奮的新特征呻征,你可以閱讀[our "what's new in swift 2.0 article"], 或者 Apple's swift blog陆赋。
你也可以在蘋果開發(fā)者入口Protocal Oriented Programming觀看WWDC Session深入理解背后的原理。