原文戳我
这个教程要求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令人兴奋的新特性.