原文戳我
這個教程要求Xcode7和Swift2,在這里還是測試版,大家可以去下載最新的.
在wwdc2015,發布了Swift2,包含了新的特性來提高你寫代碼的方式.
最令人興奮的特性是協議拓展(protocol extensions
) . 在swift1是可以拓展已存在功能性的class
,struct
,enum
類型. 在swift2,你還可以拓展protocol
.
盡管最初看起來像一個次要的功能, protocol extensions
確實非常有用,能夠改變你寫代碼的方式. 在這個教程,你會探索生成和使用protocol extensions
的幾種方式以及新的技巧和面向協議編程的設計模式,它們會讓你的代碼更好.
你也能看到Swift team怎樣使用protocol extensions
來提高Swift的標準庫的,它是如何影響你寫代碼的方式.
開始啦
先創建playground,名字為swift-Procotols. 添加以下代碼:
protocol Bird{
var name:String{get}
var canFly:Bool{get}
}
protocol Flyable{
var airspeedVelocity:Double{get}
}
這里定義了一個簡單的protocol Bird
和簡單的properties name和canFly
和 Flyable
協議,它定義 airspeedVelocity
變量.
在以前,你可能構建一個含有Flyable
的基類,然后依賴于繼承來定義Bird
或者其他能fly
的東西,例如airplanes. 在這里請記住:任何東西都從一個協議開始!
當你下一步開始定義實例的時候,你會發現它是怎樣讓整個系統更加靈活的.
定義 Protocol-conforming
類型
添加一下結構體定義到剛剛的代碼下面:
struct FlappyBird:Bird, Flyable{
let name:String
let flappyAmplitude:Double
let flappyFrequency:Double
let canFly=true
varairspeedVelocity:Double{
return 3*flappyFrequency*flappyAmplitude
}
}
這定義了一個新的結構體FlappyBird
, 它遵循Bird
和Flyable protocols
. 它的airspeedVelocity
是通過flappyFrequency
和flappyAmplitude
組成的一個函數來計算的.要flappy
,就返回sure
給canFly
.
下一步,在添加兩個結構體定義到剛才的代碼下面
structPenguin: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{return2000.0}
}
一個Penguin
是一個Bird
,但是不能fly
.這是一個你沒有利用繼承方式的一個好處. 畢竟不是所有的birds
都能fly
. 一個SwiftBird
當然是很快的,有著一個很高的airspeed velocity
.
你已經發現了還有一些冗余. 每種類型的Bird
都必須聲明canFly
屬性,盡管已經有一個Flyable概念在你的系統.
用默認實現來擴展協議extenting Protocols
有了protocol extensions
, 你能定義一個協議的默認行為. 添加下面代碼到Bird協議定義:
extension Bird where Self:Flyable{
// Flyable birds can fly!
var canFly:Bool{returntrue}
}
這定義了一個Bird protocol
的擴展,它設置了canFly
為true
的默認行為,不管類型是不是Flyable. 換言之,任何Flyable
的bird
不再需要顯性聲明了!
我不能總是定義我自己的協議,但當我定義了自己的協議,我會用默認行為來擴展它們.
Swift1.2 介紹了協議擴展是需通過if-let語法來實現的,而Swift2帶來了更好的條件擴展協議.
在FlappyBird
和SwiftBird
結構體定義中,刪除let canFly = true
. 你會發現playground成功編譯,因為protocol extension
現在會處理你的要求了.
為什么不使用基類呢?
Protocol extensions
和默認實現可能看起來和使用基類或者說是其他語言中的抽象類, 但protocol extensions
提供了一些在Swift關鍵的優勢:
因為types
可以遵循多個協議,它們可以設置多個協議的默認行為. 不像一些語言的類能夠實現多繼承,協議擴展不引入任何額外的狀態.
Protocol
能夠被classes
, struct
和enums
遵循. 基類和繼承被只能是class types
.
換言之,Protocol extensions
讓我們可以為value types
定義默認行為,而不僅僅是class
.
你已經看到這種做法應用于結構體, 下面, 添加下面的enum
定義到playground的最后.
enumUnladenSwallow:Bird, Flyable{
caseAfrican
caseEuropean
caseUnknown
varname:String{
switchself{
case.African:
return"African"
case.European:
return"European"
case.Unknown:
return"What do you mean? African or European?"
}
}
varairspeedVelocity:Double{
switchself{
case.African:
return10.0
case.European:
return9.9
case.Unknown:
fatalError("You are thrown from the bridge of death!")
}
}
}
就是其他value type
一樣, 你需要做的就是定義正確的properties
, 例如UnladenSwallow
遵循2個協議. 因為它遵循了Bird
和Flyable
協議, 所以它設置canFly
默認實現.
擴展Protocols
通常最通用的協議擴展時候是擴展額外的協議,無論是在Swift標準庫還是第三方的framework.
添加下面代碼到playground:
extension CollectionType{
func skip(skip:Int)->[Generator.Element]{
guard skip != 0 else { return[] }
var index = self.startIndex
var result:[Generator.Element] = []
vari = 0
repeat{
if i%skip == 0 {
result.append(self[index])
}
index=index.successor()
i++
} while(index != self.endIndex)
return result
}
}
這定義了CollectionType
的擴展,定義了一個新的skip(_:)
方法,這個方法跳過collection
里面每個skip
元素,返回那些未被跳過的元素組成的一個數組.
CollectionType
是一個協議,被Swift中的arrays
和dictionaries
的type
遵循. 這意味著新的行為存在于每個CollectionType
在你的App里! 添加下面的代碼到playground最下面:
letbunchaBirds:[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)
這里,你是定義一個birds
的array
,包含很多你已經定義了的類型. 因為arrays遵循了CollectionType
.那意味著,它們有skip(_:)
方法是可用.
擴展你們自己的協議
就像添加一個新的行為到來自Swift標準庫的protocols
里面,你也可以定義默認的行為.
修改Bird Protocol
,聲明遵循BooleanType protocol
:
protocol Bird:BooleanType{
遵循BooleanType
意味你的type
需要有BoolValue property
是它能像一個Boolean
. 是否意味著你必須添加在這個property
到每個Bird type
?
當然不是,有一個更簡單的方式,就是協議拓展. 添加代碼到Bird定義下面:
extension BooleanType where Self:Bird{
varboolValue:Bool{
return self.canFly
}
}
這拓展使canFly property 代表 每個Bird type的Boolean value.
嘗試,添加下面代碼到playground:
if UnladenSwallow.African {
print("I can fly!")
} else {
print("Guess I’ll just sit here :[")
}
你應該會看到”I can fly!” 被打印出來. 但尤其,你需要使用African Unladen Swallow
在一個條件語句.
對Swift標準庫的影響
你已經看到protocol extensions
是一個很好的方式來自定義和擴展你代碼的能做的事情,以及協議定義在你的aoo之外. 你可能會驚訝,Swift 協議擴展,也用于提升標準庫的寫法.
Swift通過添加map,reduce,filter
方法來提高函數編程的范例. 這些存在于不同的CollectionType
成員,例如Array:
// 計算數組中字符個數
["frog","pants"].map{$0.length}.reduce(0){$0+$1}// 返回9
對一個數組調用map
,返回另外一個數組. 而reduce
被調用減少結果到最后value 9
.
在這個例子中,map
和reduce
是包含在Array
中,是Swift標準庫的一部分. 如果你cmd-clicked在map
上,你會看到它是怎么定義的.
在Swift1.2 你會看到下面:
// Swift 1.2
extensionArray:_ArrayType{
// Return an `Array` containing the results of calling
// `transform(x)` on each element `x` of `self`
func map(transform:(T)-> U)->[U]
}
map函數在這里是定義的Array
的一個拓展. Swift的函數式的函數使用不僅在Array
上. 然而,而是在所有的CollectionType
上. 那Swift1.2是怎樣實現它的?
如果你在一個Range
上調用map
,以及Cmd-clicked 在map上,你會看到下面的定義:
// Swift 1.2
extension Range{
// Return an array containing the results of calling
// `transform(x)` on each element `x` of `self`.
func map(transform:(T)-> U)->[U]
}
結果表明在Swift1.2,在Swift標準庫里,map
需要被每個Collectiontype
重定義. 這是因為盡管Array
和Range
都是CollectionType
但是structs
不能被子類化,所以不能有相同的實現.
這不是Swift標準庫如何被構建的細微差別,這是約束了你對Swift types
能做的事情.
下面的函數,傳入Flyable
的CollectionType
返回含有最高的airspeedVelocity
的元素.
func topSpeed(collection:T)->Double{
collection.map{$0.airspeedVelocity}.reduce{max($0, $1)}
}
在Swift1.2 沒有protocol extensions
,這實際上是一個構建失誤. map
和reduce
函數只存在于一組預定義types
,并不適用于所有CollectionType
.
有Swift2.0 和Protocol extensions
, map
的定義在Array
和Range
如下:
//Swift 2.0
extension CollectionType{
// Return an `Array` containing the results of mapping `transform`
// over `self`.
///
// - Complexity: O(N).
func map(@noescape transform:(Self.Generator.Element)-> T)->[T]
}
盡管你不能看到map
的源碼. swift2會開源. CollectionType
的map
現在有一個默認的實現,所有CollectionType
都有效.
添加如下代碼:
func topSpeed(c:T)->Double{
return c.map{$0.airspeedVelocity}.reduce(0){max($0, $1)}
}
map
和reduce
方法,`Flyable實例的collection都能使用了. 現在,添加下面代碼,你就能回答,它們之中誰最快的問題了.
let flyingBirds:[Flyable]=[UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version:2.0)]
topSpeed(flyingBirds)// 2000.0
如何進行下一步學習?
你已經看到了面向協議開發, 建立自己的簡單的協議,使用協議擴展來擴展它們. 用默認的實現,你可以給已存在的協議添加共有的或自動的行為,很像一個基類,但比基類更好,因為能應用到struct和enums.
另外,協議擴展不僅可以使用到擴展你自己的協議,也可以擴展和提供默認行為到Swift標準庫,Cocoa和Cocoa Touch的協議里.
你應該看看Swift2.0令人興奮的新特性.