看到了Raywederlich的這篇文章,覺得寫得簡單易懂, 為了更加深刻的理解也希望把這篇文章推廣給更多的人,在此翻譯出來:
在WWDC2015中,蘋果發(fā)布了Swift 2.0,它包含了一些新的特性 以便于我們更好地編寫代碼.
這些激動人心的特性之一就是 協(xié)議擴展. 在Swift的第一個版本中, 我們可以為 類 , 結(jié)構(gòu)體 和 枚舉 添加擴展. 現(xiàn)在在Swift 2.0中,你同樣可以為一個 協(xié)議 添加擴展.
它乍看上去像是一個不那么重要的特性,但是協(xié)議擴展是一個非常強大的特性并且可以改變你編碼的方式. 在這個教程中, 我們將會探索創(chuàng)建和使用協(xié)議擴展的方式,蘋果開放的新技術(shù)以及面向協(xié)議編程模式.
你也會看到Swift團隊是如何用協(xié)議擴展來完善Swift標準庫的.
開始
創(chuàng)建一個playground, 名字也替你想好了,不用糾結(jié),就叫SwiftProtocols吧.你可以選擇任何平臺,因為這個教程中的代碼是跨平臺的.
playground打開之后,添加以下代碼:
protocol Bird {
var name: String { get }
var canFly: Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
這幾句代碼定義了一個簡單的 Bird 協(xié)議, 它擁有 name 和 canFly 兩個屬性; 和一個 Flyable 協(xié)議,有一個 airspeedVelocity 屬性.
在面向?qū)ο蟮氖澜缋? 你可能會定義 Flyable 作為基類,然后讓 Bird 還有其他能飛的東西繼承自它, 比如飛機.但是在面向協(xié)議的世界中, 一些事物是以協(xié)議為起始的.
在下面定義真正類型的時候你將會看到面向協(xié)議是如何讓整個系統(tǒng)更加靈活的.
定義遵循協(xié)議的類型
添加下面的結(jié)構(gòu)體到playground的底部:
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 , 它遵循了 Bird 和 Flyable 協(xié)議.它的 airspeedVelocity 是一個跟 flappyFrequency 和 flappyAmplitude 有關(guān)的計算型屬性. 既然是* flappy (意為飛揚的), 它的 canFly *屬性必然為true. :]
下一步, 添加下面的兩個結(jié)構(gòu)體到playground的底部:
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 is FAST!
var airspeedVelocity: Double { return 2000.0 }
}
一個 Penguin (企鵝)是一個* Bird* (鳥類), 但是是不能飛的鳥類.A-ha~ 辛虧我們沒有使用繼承關(guān)系,把* Bird* 寫成類繼承自Flyable,讓所有的鳥類都必須會飛,要不然企鵝得有多蛋疼.一個* SwiftBird (swift在英文中是雨燕的意思,因為雨燕飛的很快,所以swift也有迅速之意)當(dāng)然是擁有高的 airspeed velocity* 非常快的鳥(傲嬌臉).
現(xiàn)在你已經(jīng)能看到一些代碼冗余.每一個遵守* Bird* 協(xié)議的類型都必須聲明它是不是* canFly* , 盡管你的系統(tǒng)中已經(jīng)有了一個* Flyable* 的概念.
用默認行為來擴展協(xié)議
你可以用協(xié)議擴展定義一個協(xié)議的默認行為.添加下面的代碼到* Bird* 協(xié)議下面:
extension Bird where Self: Flyable {
// Flyable birds can fly!
var canFly: Bool { return true }
}
這個* Bird* 擴展讓所有同樣遵循了 Flyable 協(xié)議的類型的canFly屬性返回true. 也就是說, 遵守了 Flyable 的* Bird* 都不用再明確地聲明* canFly* 了.
Swift1.2在if - let 的綁定使用中引入了where語法.Swift2.0讓我們在我們的協(xié)議擴展需要一個約束條件時同樣能夠使用它.
在* FlappyBird* 和 * SwiftBird* 結(jié)構(gòu)體中刪除* let canFly = true* . playgroud運行良好, 因為協(xié)議擴展已經(jīng)替你處理了那個需求.
為什么不用基類?
協(xié)議擴展和默認實現(xiàn)有些像基類或者其他語言中的抽象類, 但是它在Swift中有一些核心優(yōu)勢:
- 類型可以遵循多個協(xié)議,所以他們可以有很多默認行為. 不像其他語言支持的類的多繼承, 協(xié)議擴展不會帶來額外的狀態(tài)(這里因為對多繼承不是很了解,所以直接翻譯了,有人給我推薦了喵神的這篇文章,提到了多繼承的菱形缺陷, 不知道這里所謂的額外狀態(tài)是不是指菱形缺陷,比較懂的朋友還請指點一二)
- 除了類,結(jié)構(gòu)體和枚舉也可以使用協(xié)議.而基類和繼承只局限于類,結(jié)構(gòu)體和枚舉用不了
換句話說,協(xié)議擴展讓值類型可以擁有默認行為.
上面我們已經(jīng)說了協(xié)議擴展在結(jié)構(gòu)體中的使用,下面來看看枚舉, 將下面的代碼加到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!")
}
}
}
只是換了個類型而已,和結(jié)構(gòu)體沒太大區(qū)別,不細述.
你不會真的認為這篇教程用到的* airspeedVelocity* 不是用的蒙提派森的梗吧??? (這里解釋一下,* Monty Phython* 又譯為巨蟒劇團吮蛹、蒙提巨蟒牙躺、踎低噴飯蹭越,是英國的一組超現(xiàn)實幽默表演團體.而上面的* UnladenSwallow* 枚舉是源自于他們一個劇的對話,感興趣的可以去搜下)
擴展協(xié)議
協(xié)議擴展最常用的就是擴展外部協(xié)議, 不論它是定義在Swift標準庫中還是第三方庫中
將下面??的代碼加到playground的底部:
extension Collection {
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() Swift3中作了以下改變
index = self.index(after: index)
//Swift3中不允許用i++了
i += 1
} while (index != self.endIndex)
return result
}
}
這段代碼定義了* CollectionType* (Swift3改成了Collection)的一個擴展,里面添加了* skip(_:)* 方法, 這個方法會跳過所有給定條件的元素,然后返回沒有被跳過的元素集合.
CollectionType 是一個被Swift中比如數(shù)組,字典這樣的集合類型所遵循的協(xié)議.這意味著新增的這個方法所有的集合類型都可以用.
又來啦,把下面的代碼添加到playground底部:
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(skip: 3)
這里我們定義了一個鳥類的數(shù)組,因為數(shù)組遵守了* CollectionType* 的協(xié)議,所以也能調(diào)用* skip(_:)* 方法.
擴展你自己的協(xié)議
我們不光可以像上面一樣給標準庫中的協(xié)議擴展方法,還可以添加默認行為.
修改* Bird* 協(xié)議生命讓它遵循* BooleanType* 協(xié)議:
protocol Bird: BooleanType {
遵守* BooleanType* 協(xié)議意味著你的類型得有一個* boolValue* . 難道我們要把這個屬性添加到每一個* Bird* 類型嗎???
當(dāng)然不,將下面的代碼添加到* Bird* 定義下面:
extension BooleanType where Self: Bird {
var boolValue: Bool {
return self.canFly
}
}
這個擴展讓* canFly* 屬性代表了每個* Bird* 類型的布爾值.
看這個是否成立,我們試試下面的代碼,同樣加到playground底部:
if UnladenSwallow.African {
print("I can fly!")
} else {
print("Guess I’ll just sit here :[")
}
非布爾值是不能直接在if后面做true or false的判斷的, 但是這里卻可以了,就是因為* Bird* 遵循了* BooleanType* , 而* UnladenSwallow.African* 的* canFly* 值是true, 所以它的* boolValue* 也是true.
對于Swift標準庫的影響
上面我們已經(jīng)看到協(xié)議擴展怎樣幫助我們自定義和擴展我們的代碼功能.更加讓你感到驚訝的會是Swift項目組如何運用協(xié)議擴展來完善Swift標準庫.
Swfit引入了map, reduce, 和 filter 方法來促進函數(shù)式編程.這些方法用在集合類型中, 比如數(shù)組:
//計算數(shù)組中所有元素字符數(shù)之和
let result = ["frog","pants"].map { $0.lengthOfBytes(using: .utf8) }.reduce(0) { $0 + $1 }
print(result)
調(diào)用數(shù)組的* map* 函數(shù)返回另外一個數(shù)組,這個數(shù)組里面盛放的是原數(shù)組的每個元素字符數(shù),即[4,5], 這個數(shù)組又調(diào)用了 reduce 函數(shù)來計算二者之和,結(jié)果返回9.
Cmd-Click進入 map 函數(shù)源碼可以看到它的定義.
Swfit 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 的extension中的, 可是這些功能性的函數(shù)不止能被 Array 使用, 它們可以被任何集合類型所調(diào)用, 那么Swift 1.2中是怎么實現(xiàn)的呢?
如果你用一個 Range 類型調(diào)用 map 方法, 然后Cmd-Click map函數(shù)進入源碼,能看到下面的代碼:
// 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]
}
結(jié)果是Swift 1.2中,所有的集合類型都要定義一遍 map 函數(shù), 這是因為雖然 Array 和 Range 都是集合類型,但是結(jié)構(gòu)體是不能被繼承的.
這不是一個小差別,它會限制你使用Swift的類型.
下面的泛型函數(shù)適用于那些遵循了 Flyable 的集合類型,并返回一個只有一個元素的數(shù)組, 這個元素就是集合類型中所有元素的最大 airspeedVelocity :
func topSpeed<T: CollectionType where T.GeneratorType: Flyable>(collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1) }
}
Swift 1.2中這個函數(shù)會報錯, map 和 reduce 等函數(shù)只能被標準庫中已經(jīng)定義的集合類型使用, 像這里我們自己定義的遵守 Flyable 的集合類型是不能使用這些函數(shù)的.
Swift 2.0中, 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 函數(shù)了.
將上面的泛型函數(shù)加到playground的底部:
func topSpeed<T: CollectionType where T.GeneratorType: Flyable>(collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1) }
}
運行良好?? . 現(xiàn)在我們可以看看到底這些鳥類中誰是最快的! :]
let flyingBirds: [Flyable] =
[UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version: 2.0)]
topSpeed(flyingBirds) // 2000.0
還用說?? .
后話: 終于翻譯完了,累成狗?? . 看在我這么拼的份上,轉(zhuǎn)載請注明出處:]