本篇文章翻譯自:Introducing Protocol-Oriented Programming in Swift 2)
原作:[Erik kerber]
(https://www.raywenderlich.com/u/kerber) on June 25, 2015
注: 這篇tutorial需要XCode 7, Swift2, 這個秋天會發布測試版。你也可以在 Apple's developer portal下載最新版本。
WWDC 2015, 蘋果發布了swift 2,宣布了swift語言第二次重大修訂,其中包括了幾個新的語言特征,以幫助你提升編碼方式。
在這些的新特征中,最令人興奮的是協議擴展。在swift第一個版本,你可以擴展已經存在的類,結構體,和枚舉類型的功能。現在有了swift 2,你也可以擴展協議。
可能剛開始你會覺得這是一個微不足道的特征,但是協議擴展真的能量巨大,且可以轉變你的編碼方式。在這篇turorial,你會探索創建,使用協議擴展的方法,還有新技術和面向協議編程模式(以下簡稱:POP)。
你將會看到Swift團隊是怎樣使用協議擴展以提升swift標準庫,和協議擴展怎樣影響你的代碼。
開始
開始在XCode中創建一個新的Playground, 選擇File\New\Playground...,然后命名Playground為SwiftProtocols。你可以選擇任何平臺,因為這篇tutorial所有的代碼跟平臺無關。點擊Next選擇你要保存的路徑,然后點擊Create.
Playground打開后,我們添加如下代碼:
protocol Bird {
var name: String { get }
var canFly:Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
}
這里簡單的定義了一個擁有2個屬性name, 和canFly的Bird協議類型,還有一個Flyable類型,它定義了airspeedVelocity屬性。
在pre-protocol時期,你可能會把Flyable當做基類,然后依賴繼承去定義Bird還有其他能fly的東西,例如:灰機。請記住:一切始于協議!
定義遵守協議的類型
在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
}
}
這里定義了一個新的結構體FlappyBird, 它遵守Bird和Flyable協議,airspeedVelocity為計算屬性。canFly屬性設置為true。
接下來,在playground下面添加以下2個結構體的定義:
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
}
}
企鵝是鳥類,但不會飛。哈--- 還好你沒有使用繼承,讓鳥類都能飛。海燕當然有非常快的飛行速度。
你可能已經看到一些冗余。盡管在你的結構中已經有了Flayable的概念,每一種Bird類型還必須聲明是否canFly。
用默認實現擴展協議
使用協議擴展,你可以為協議定義默認行為。在Bird協議下添加如下代碼:
extension Bird where Self: Flyable {
var canFly: Bool { return true }
}
這里定義Bird的一個擴展,當類型也為Flyable時,設置為canFly設置默認行為true。換句話說,任何一個遵守Flyable的Bird類型不必在顯示聲明。
swift 1.2 引入了where語法,之后swift 2使用where語法給協議擴展添加約束(Self作用是指,當Bird類型同時也遵守Flyable協議,canFly屬性才能生效)
現在從FlappyBird和SwiftBird結構體聲明中刪除let canFly = true。你會發現playground成功編譯了,因為協議擴展幫你處理了協議的要求。
為什么不用基類
協議擴展和其默認實現好像跟其他語言的基類或者抽象類很相似,但是swift具有一下幾個關鍵優勢:
- 因為類型可以遵守多個協議,可以擁有多個協議默認實現的功能。不像其他編程語言支持的類的多繼承,協議擴展不會引入額外的狀態。
- 協議可以被類,結構體和枚舉類型遵守。基類和繼承受限于類類型(class類型)
換句話說,協議擴展為值類型,而不僅僅是為類類型提供了定義默認行為的能力。
你已經在結構體中見識過這種行為;接下來在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協議,你只需要定義協議要求的屬性。因為它遵守這2個協議,所以UnladenSwallow的canFly屬性也獲得了默認實現。
你真的認為該 tutorial只會包括airspeedVelocity屬性,而不會包括Monty Python的引用。(哈,可擴展。。。)
擴展協議
可能協議擴展最普遍的用法就是擴展外部協議了,可能是定義在swift標準庫的協議,也可能是定義在第三方框架的協議。在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類型上擴展了一個新的方法skip(:), 它會遍歷集合中的每一個元素,過濾掉不符合要求的元素(留下索引值能被參數skip整除對應的元素),然后返回一個過濾后的數組。
CollectionType是一個被諸如swift中數組,字典等類型遵守的協議。這就意味著這個新的行為(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)
這里,你定義了一個包含各種類型鳥類的數組,這些類型上文中你已經定義過。因為數組遵守CollectionType, 那么它就能用skip(_:)方法。
擴展你自己的協議
也許讓你興奮的是你可以像給swift標準庫添加新的行為一樣,你也可以給自己的協議定義新的行為。
修改Bird協議聲明,使之遵守BooleanType協議:
protocol Bird: BooleanType {
var name: String { get }
var canFly: Bool { get }
}
遵守BooleanType協議意味著你的類型需要有一個Bool類型的 boolValue屬性。那么是不是意味著你必須給現在或者之后的每一個Bird類型都添加這么個屬性呢?
當然,我們有更容易的辦法———協議擴展。在Bird定義下添加如下代碼:
extension BooleanType where Self: Bird {
var boolValue: Bool {return self.canFly}
}
這個擴展讓canFly屬性代表了每一個Bird類型的Boolean 值。playground中添加如下代碼,一起來見證奇跡:
if UnladenSwallow.African {
print("I can fly!")
}else {
print("Guess I'll just sit here")
}
你會看到"I can fly!" 出現在輔助編輯器上。但是這里你僅是在if 語句中使用了UnladenSwallow。(你可以挖掘更多用法!!!)
對swift標準庫的影響
你已經見證過了協議擴展是多么棒--- 你可以使用它自定義和擴展你自己的代碼的能力,還有你APP之外的協議(標準庫和第三方框架協議)。也許你會好奇,swift團隊是怎么使用協議擴展來提升swift標準庫的。
swift通過向標準庫中添加諸如map, reduce和filter等高階函數來提升函數式編程范式。這些方法存在不同CollectionType類型中,例如:Array:
["frog", "pants"].map{ $0.length}.reduce(0){$0 + $1}
array調用map會返回另一個array, 新的array調用reduce來歸納結果為9.(map會遍歷數組中每一個元素,并且返回由數組長度組成的數組,reduce把新數組中元素長度做加運算)
這種情況下,map和reduce作為swift標準庫的一部分被包含在Array中,如果你按下cmd,點擊map,你可以看到它怎么定義的:
extension Array : _ArrayType {
func map(transform: (T) -> U) -> [U]
}
這里的map函數被定義為Array的一個擴展. swift的函數范式工作范圍遠不止Array, 它在任意的CollectionType類型上都能工作。所以swift 1.2下是怎么工作的呢?
按下cmd,點擊Range的map函數,你將看到以下定義:
extension Range {
func map(transform: (T) -> U) -> [U]
}
這些表明在swift 1.2中, swift標準庫中的各種CollectionType類型需要重新定義map函數。因為盡管Array和Range都是CollectionType,但是結構體不能被子類化,也沒有統一的實現。
這并不僅僅是swift標準庫做出的細微差別,也對你使用swift類型做出約束。
下面的泛型函數接受一個遵守Flyable協議的CollectionType類型參數,返回airspeedVelocity最大的元素。
func topSpeed(collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}
在swift 1.2中沒有協議擴展,這會出現編譯錯誤。map和reduce函數只會存在于預先定義的類型中,不會在任意的CollectionType都會工作。
在swift 2.0中有了協議擴展,Array和Range的map定義如下:
extension CollectionType {
func map(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}
盡管你不能看到map源碼---至少到swift 2開源(目前已經開源了),ColletionType有map的一個默認實現,而且所有的CollectionType都能免費獲得??。
在playground下面添加早先提到的泛型函數:
func topSpeed(c: T) -> Double {
return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
map和reduce函數可以用在你的Flyable實例集合中了。現在你終于可以回答他們中誰是最快的。
let flyingBirds: [Flyable] =[
UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version: 2.0)
]
topSpeed(flyingBirds) // 2000.
結果應該無人質疑吧??(海燕最快)
延伸閱讀
你可以在這里下載本tutorial完整playground代碼。
你已經見識過POP的威力了,創建你自己的簡單協議,然后使用協議擴展來擴展他們。有了默認實現,你可以給已經存在的協議一個統一的實現,有些像基類但是優于基類,因為它還可以應用自愛結構體和枚舉類型上。
另外,協議擴展不僅能夠勇子啊你自定義的協議上,還可以擴展swift標準庫,Cocoa, CocoaTouch中協議的默認行為。
如果想要瀏覽swift 2.0令人興奮的新特征,你可以閱讀[our "what's new in swift 2.0 article"], 或者 Apple's swift blog。
你也可以在蘋果開發者入口Protocal Oriented Programming觀看WWDC Session深入理解背后的原理。