本篇文章翻译自: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深入理解背后的原理。