本文翻譯自:Ray Wenderlich
原文地址:Introducing Protocol-Oriented Programming in Swift 2
![Swift Bird brings speedy new features to Swift 2!](http://www.swiftyper.com/usr/uploads/2015/06/955131448.jpg)
說明:本教程使用了最新的 beta 版 Xcode 7 與 Swift 2.0,它們的正式版將會在今年的秋季發(fā)布米愿。目前可以到蘋果的開發(fā)者官網(wǎng)上下載最新的 beta 版 Xcode扼睬。
在 WWDC 2015上毙芜,蘋果發(fā)布了 Swift 2.0 版本螟左,這是自 Swift 發(fā)布以來進行的第二次重大改版欢揖,這次改進推出了很多新特性來幫助我等程序員寫出更優(yōu)雅的代碼营罢。
在這些新特性里面翼虫,最讓人興奮的莫過于 協(xié)議擴展(protocol extensions) 了。在第一版的 Swift 當中沉帮,我們可以使用擴展來為 類(class)、結(jié)構(gòu)體(struct) 以及 枚舉(enum) 增加新功能贫堰。在新版的 Swift 2.0 當中穆壕,我們也可以對 協(xié)議(protocol) 進行擴展了。
這乍看起來好像只是一個很小的改變其屏,事實上喇勋,協(xié)議擴展的功能是相當強大的,它甚至能改變我們編寫代碼的方式偎行。在本教程中川背,你不僅可以學習到創(chuàng)建和使用協(xié)議擴展的方法,同時還可以體會到這項新技術(shù)和面向協(xié)議編程范式給你帶來的新視野蛤袒。
同時我們還能看到 Swift 開發(fā)小組是如何使用協(xié)議擴展來對 Swift 的標準庫進行改進熄云,以及它將會對我們編寫代碼帶來怎樣的影響。
從這里開始(Getting Started)
我們先從新建一個 playground
開始妙真。打開 Xcode 7 選擇 File\New\Playground... 并命名為 SwiftProtocols缴允。可以選擇任意的平臺珍德,因為本教程中的代碼是與平臺無關(guān)的练般。選擇保存的位置后點擊 Next矗漾,最后點擊 Create。
打開 playground
后薄料,添加如下代碼:
protocol Bird {
var name: String { get }
var canFly: Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
這里定義了一個簡單的 Bird
協(xié)議敞贡,它擁有 name
和 canFly
兩個屬性,同時還定義了一個擁有 airspeedVelocity
屬性的 Flyable
協(xié)議摄职。
在沒有協(xié)議的遠古時期嫡锌,我們可能會將 Flyable
定義為一個基類,然后使用繼承的方式來定義 Bird
以及其它會飛的東西琳钉,比如飛機之類的势木。然而這里并不用這么做,這里的所有一切都是從協(xié)議開始的歌懒。
當我們接下來開始定義具體類型的時候啦桌,你將會看到這種方法將會使我們的整個系統(tǒng)更加靈活。
定義遵守協(xié)議的類型
在代碼區(qū)底部增加如下的 結(jié)構(gòu)體 定義:
struct FlappyBird: Bird, Flyable {
let name: String
let flappyAmplitude: Double
let flappyFrequency: Double
let canFly = true
var airspeedVelocity: Double {
return 3 * flappyFrequency * flappyAmplitude
}
}
這里定義了一個新的結(jié)構(gòu)體 Flappybird
及皂,這個結(jié)構(gòu)體遵守了 Bird
和 Flyable
協(xié)議甫男。它的 airspeedVelocity
屬性的值是通過 flappyFrequency
和 flappyAmplitue
計算出來的。作為一只像素鳥验烧,它的 canFly
當然是返回 true
的 :]板驳。
接著,再定義兩個結(jié)構(gòu)?體:
struct Penguin: Bird {
let name: String
let canFly = false
}
struct SwiftBird: Bird, Flyable {
var name: String { return "Swift \(version)" }
let version: Double
let canFly = true
// Swift 速度超群!
var airspeedVelocity: Double { return 2000.0 }
}
企鵝(Penguin
)是一種鳥(Bird
)碍拆,不過它是不能飛的若治。啊哈,這時應該慶幸我們沒有采用繼承的方法感混,使用繼承會讓所有的子類都擁有飛的能力端幼。雨燕鳥(SwiftBird
)不僅能飛,它還擁有超快的速度弧满!
我們可以發(fā)現(xiàn)婆跑,上面的代碼里面有一些冗余。盡管我們已經(jīng)有了 Flyable
的信息庭呜,但是我們還是得為每個 Bird
類型指定 canFly
屬性來表明它是否可以飛行滑进。
擁有默認實現(xiàn)的擴展協(xié)議
對于協(xié)議擴展,我們可以為它指定默認的實現(xiàn)募谎。在定義 Bird
協(xié)議的下方增加如下代碼:
extension Bird where Self: Flyable {
// Flyable birds can fly!
var canFly: Bool { return true }
}
這里通過對 Bird
協(xié)議進行擴展扶关,為它增加了默認行為。當一個類同時遵守 Bird
和 Flyable
協(xié)議時近哟,它的 canFly
屬性就默認返回 true
驮审。即是說,所有遵守 Flyable
協(xié)議的鳥類都不必再顯式指定它是否可以飛行了。
Swift 1.2 將 where
判斷語法增加到了 if-let 綁定中疯淫,而 Swift 2.0 更進一步地將這個語法帶到了協(xié)議擴展中地来。
將 FlappyBird
和 SwiftBird
結(jié)構(gòu)體定義中的 let canFly = true
語句刪除∥醪簦可以看到未斑,playground
可以順利通過編譯,因為擴展協(xié)議的默認實現(xiàn)已經(jīng)幫你處理了這些瑣事币绩。
![](http://www.swiftyper.com/usr/uploads/2015/06/1238146152.png)
為何不使用基類蜡秽?
或許你會發(fā)現(xiàn),使用協(xié)議擴展及其它的默認實現(xiàn)與使用基類缆镣,或者其它語言中的抽象類很相似芽突,但是它有幾個關(guān)鍵的優(yōu)勢:
- 因為一個類型可以遵守多個協(xié)議,所以它可以從各個協(xié)議中接收到不同的默認實現(xiàn)董瞻。與其它語言中所支持的多重繼承不同(吐槽:說的就是C++吧)寞蚌,協(xié)議擴展不會為遵守它的類型增加額外的狀態(tài)。
- 所有的類钠糊,結(jié)構(gòu)體和枚舉都可以遵守協(xié)議君躺,而基類只能被類所繼承篮奄。
換句說就是,協(xié)議擁有為值類型(value types)增加默認實現(xiàn)的能力园细,而不僅僅是類横漏。
我們已經(jīng)體驗過了結(jié)構(gòu)體的實戰(zhàn)懊悯,接下來在 playground
的底部增加如下的枚舉的定義:
enum UnladenSwallow: Bird, Flyable {
case African
case European
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!")
}
}
}
與其它值類型一樣凛捏,你所需要做的就是定義一些屬性吞瞪,好讓 UnladenSwallow
能夠遵守這兩個協(xié)議。因為這個枚舉同時遵守了 Bird
和 Flyable
所以它也得到了 canFly
的默認實現(xiàn)笛臣。
看到這里云稚,你是不是以為這篇教程只是為了演示一些小鳥的飛行把戲?
接下來沈堡,讓我們看一些更有實戰(zhàn)意義的代碼。
擴展協(xié)議(Extending Protocols)
協(xié)議擴展最常用的約莫就是擴展外部協(xié)議了燕雁,不管這些協(xié)議來自 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++
} while (index != self.endIndex)
return result
}
}
這里對標準庫中的 CollectionType
進行了擴展,定義了一個方法拐格。這個方法會對一個集合類型中的元素以 skip
步進行跳躍僧免,然后返回集合中沒有被跳過的元素。
在 Swift 中捏浊,CollectionType
協(xié)議被類似數(shù)組以及字典這樣的集合類型所遵守懂衩。這意味著,現(xiàn)在你的整個 app 中,所有遵守 CollectionType
的類型都擁有這個方法了浊洞。接著在底部增加下面的代碼:
let bunchaBirds: [Bird] =
[UnladenSwallow.African,
UnladenSwallow.European,
UnladenSwallow.Unknown,
Penguin(name: "King Penguin"),
SwiftBird(version: 2.0),
FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)]
bunchaBirds.skip(3)
這里我們定義了一個數(shù)組牵敷,這個數(shù)組包含了大部分之前定義過的鳥類。因為數(shù)組類型遵守了 CollectionType
協(xié)議法希,所以枷餐,我們可以直接對這個數(shù)組使用 skip
方法。
擴展自己的協(xié)議(Extending Your Own Protocols)
跟為標準庫增加方法同樣令人興奮的是苫亦,我們也可以為它增加默認行為毛肋。
修改鳥類的協(xié)議,使其遵守 BooleanType
協(xié)議:
protocol Bird: BooleanType {
遵守 BooleanType
協(xié)議意味著所有 Bird
類型都要有一個 boolValue
屬性屋剑,使得它能夠像布爾值一樣被使用润匙。這是不是說我們得為所有的定義過的,還有將來要定義的 Bird
類型添加這個屬性唉匾?
當然不是孕讳,使用協(xié)議擴展能讓我們有更簡便的辦法。在 Bird
的定義下面添加如下代碼:
extension BooleanType where Self: Bird {
var boolValue: Bool {
return self.canFly
}
}
這個擴展可以讓 canFly
屬性來表示每個 Bird
類型的布爾值肄鸽。
通過下面的代碼來試驗一下:
if UnladenSwallow.African {
print("I can fly!")
} else {
print("Guess I’ll just sit here :[")
}
可以看到控制臺打印出了 "I can fly!"卫病。然而更值得注意的是,我們這里直接把 UnladenSwallow.African
丟到了 if 判斷里面典徘!
對 Swift 標準庫的影響
我們已經(jīng)看到了蟀苛,使用協(xié)議擴展可以極大地增強了我們代碼的功能和可定制性。然而你可能不知道的是逮诲,Swift 開發(fā)小組甚至使用了協(xié)議擴展對 Swift 標準庫的寫法進行了改進帜平。
Swift 通過在標準庫中增加了 map
、reduce
和 filter
等方法梅鹦,使它自身的函數(shù)式編程屬性得到了大大的提升裆甩。
這些方法存在于不同的 CollectionType
成員中,比如 Array
:
// Counts the number of characters in the array
["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // returns 9
對數(shù)組調(diào)用 map
方法返回了另一個數(shù)組齐唆,再對這個數(shù)組調(diào)用 reduce
方法嗤栓,使其計算出整個數(shù)組中的字符數(shù)。
在這里箍邮,map
和 reduce
是包含在 Array
當中的茉帅。如果我們按住 command
鍵點擊 map
,就可以看到它的定義锭弊。
在 Swift 1.2 里堪澎,我們可以看到類似下面的定義:
// Swift 1.2
extension Array : _ArrayType {
/// Return an `Array` containing the results of calling
/// `transform(x)` on each element `x` of `self`
func map<U>(transform: (T) -> U) -> [U]
}
在這里 map
函數(shù)是作為 Array
的擴展被定義的。但是 Swift 的函數(shù)式函數(shù)不止是對 Array
味滞,而是對所有的 CollectionType
都起作用樱蛤,那么 Swift 1.2 是如何處理的呢钮呀?
如果對一個 Range
類型調(diào)用 map
函數(shù),并且從那里跳到它的實現(xiàn)昨凡,我們可以看到如下的定義:
// Swift 1.2
extension Range {
/// Return an array containing the results of calling
/// `transform(x)` on each element `x` of `self`.
func map<U>(transform: (T) -> U) -> [U]
}
可以發(fā)現(xiàn)爽醋,對于 Swift 1.2 來說,標準庫中不同的 CollectionType
都需要重新實現(xiàn) map
函數(shù)土匀。
這是因為雖然 Array
與 Range
都遵守了 CollectionType
協(xié)議子房,但是由于結(jié)構(gòu)體不能被繼承,因此也就無法定義通用的實現(xiàn)就轧。
這不僅僅是標準庫實現(xiàn)上的一點細微差別证杭,這實際上限制對 Swift 類型的使用。
下面這個范型函數(shù)接受一個 Flyable
類型的 CollectionType
妒御,然后返回擁有最快速度(airspeedVelocity
)的元素:
func topSpeed<T: CollectionType where T.GeneratorType: Flyable>(collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}
在 Swift 1.2 當中沒有協(xié)議擴展解愤,因此這段代碼會報錯。map
和 reduce
函數(shù)只存在預定義的一些類型中乎莉,并不能對任意的 CollectionType
起作用送讲。
然而在 Swift 2.0 中使用了協(xié)議擴展,對于 Array
和 Range
的 map
函數(shù)都是這樣定義的:
// Swift 2.0
extension CollectionType {
/// Return an `Array` containing the results of mapping `transform`
/// over `self`.
///
/// - Complexity: O(N).
func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}
雖然無法看到 map
方法的實現(xiàn) -- 至少在 Swift 2.0 開源之前惋啃,但是我們可以知道所有的 CollectionType
都有一個 map
方法的默認實現(xiàn)哼鬓。即是說,所有遵守 CollectionType
的類型边灭,都能隨機附贈一個 map
方法异希。
在 playground
的最底部增加如下的泛型函數(shù):
func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(c: T) -> Double {
return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
可以對保存 Flyable
類型的集合調(diào)用 map
和 reduce
方法了。如果绒瘦,你還對上面定義的鳥類中哪個速度最快持有疑惑的話称簿,就可以使用這個函數(shù)來得到最終的答案了:
let flyingBirds: [Flyable] =
[UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version: 2.0)]
topSpeed(flyingBirds) // 2000.0
接下來做什么(Where To Go From Here)
你可以在這里下載到完整的 `playground。
通過定義自己的簡單協(xié)議惰帽,并對它們使用協(xié)議擴展憨降,我們已經(jīng)見識到了面向協(xié)議編程的強大了。通過默認實現(xiàn)该酗,我們可以給已經(jīng)存在的協(xié)議增加通用和默認的行為授药,這與使用基類相似,但是更靈活呜魄,因為它也適用于結(jié)構(gòu)體和枚舉烁焙。
更進一步,協(xié)議擴展不僅可以用來擴展自定義的協(xié)議耕赘,還可以對 Swift 標準庫,Cocoa 和 CocoaTouch 的協(xié)議進行擴展膳殷,并提供默認行為操骡。
如果想知道 Swift 2 還更新了哪些其它新特性九火,可以參考我們的另一篇文章Swift 2 新特性,或者 Swift 2 公布的官方博客册招。
同時還可以觀看 WWDC 的Protocol Oriented Programming來進行更加深入的學習岔激,以及獲得更底層的理論知識。
最后再打個小廣告:)是掰,本人博客地址:Swiftyper