Swift Bird brings speedy new features to Swift 2!
备注:此教程需要安装Xcode 7及Swift 2,此时两者均可能处于beta版本。您可以通过苹果开发者门户网站下载最新的beta版本。『译者注:原文发布于2015.6.25,当时Xcode 7及Swift 2尚处于beta版本』。
在WWDC2015,苹果发布了包含众多新语言特性的Swift 2,帮助开发者改善他们的编码方式。
其中,最让人兴奋的要属协议扩展(protocol extensions)
语法。在Swift 1中,开发者可以扩展原有的class, struct和enum类型。现在,开发者可以对protocol进行扩展了。
刚看到protocol extensions时,大家可能认为这只是一个相对次要的功能,但实际上protocol extensions非常强大,强大到甚至可以改变开发者的编码方式!在这个教程中,你将会跟随我们一起探索如何使用protocol extensions语法,以及如何进行「协议式编程」
。
开始
在Xcode中,选择File\New\Playground...
打开一个新的playground,并将playground命名为SwiftProtocols
。由于本教程可以用在任意平台上,platform可以随意选择。点击Next
,保存Playground。
创建成功后,输入下述代码:
protocol Bird {
var name: String { get }
var canFly: Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
这里我们定义了一个简单的protocol Bird
,包含属性name
和canFly
,同时定义了一个Flyable
协议,包含属性airSpeedVelocity
。
在没有protocol
的时候,大家一般会将Flyable
定义为基类,然后通过继承的方式定义Bird
及其他可以「飞」的类,例如飞机。在这里我们需要明确一点,所有的事情都由protocol开始!
当你开始定义类型时,你将会逐步发现「协议式编程」是如何使整个系统更加灵活健壮的。
定义遵循protocol的类型
在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
两个protocol。其中,属性airspeedVelocity
是通过闭包的方式计算返回的。同时,由于可以飞行,所以canFly
参数直接返回true。
接下来,在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
var airspeedVelocity: Double { return 2000.0 }
}
在这里,企鹅Penguin
同样属于鸟类,但是不能飞。啊哈~这就体现出不使用类继承的好处了,若使用了类继承的方式,则所有子类中的canFly
属性都只能统一了!雨燕SwiftBird
是很快的,所以airspeedVelocity
属性返回了2000.0,一个非常快的飞行速度。
到此,相信你已经发现一些多余的代码了。每种类型的Bird
都要重新定义canFly
是true
还是false
,尽管Flyable
已经暗示了canFly = true
。
扩展协议,使其包含默认的实现
通过protocol extensions
语法,开发者可以给一个protocol
定义默认的执行方法。在Bird
协议下,添加如下代码:
extension Bird where Self: Flyable {
var canFly: Bool { return true }
}
在此,我们给协议Bird
指定了一个默认的执行方法:「如果该类型包含了Flyable
协议,则canFly
返回true
」。这样,所有Flyable
的bird都不需要另外再明确声明canFly = true
了。
Swift 1.2中介绍了where
语法在if-let
中的使用,而Swift 2则给我们带来了有条件地扩展protocol的能力。
接下来,在FlappyBird
和SwiftBird
结构体中删除let canFly = true
语句,你会发现playground运行一切正常,因为,protocol extension
已经帮你处理好canFly
属性了。
为什么不使用基类
协议扩展(protocol extensions)及其默认执行方法看起来可能跟使用基类和抽象类(abstract type)相似,但Swift有几点优势:
- 由于类型可以遵循多个protocol,因此可以被多个protocols的默认行为修饰。与多重继承的方式不同,协议扩展不会引入额外的属性及状态。
- protocol可以被class、struct及enum遵循,而基类和多重继承均只能限制在类之间使用。
换句话说,协议扩展提供了让value类型也能用上默认执行方法,而不仅是类专有了。
按照上述步骤,相信开发者已经知道如何在struct中应用protocol了;接下来,我们尝试在enum类型中应用protocol。请在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!")
}
}
}
跟其他value类型一样,在enum
中,开发者只需给协议中的属性定义正确的实现。由于UnladenSwallow
同时遵循Bird
和Flyable
,故canFlay
属性不需要定义其实现,默认为true
。
至此,你还认为本教程中的airspeedVelocity
不会包含一个蟒蛇的引用吗?『译者注:水平有限,此处翻译生硬,原文为「Did you really think this tutorial involving airspeedVelocity
wouldn't include a Monty Python reference?:]」』
协议扩展的应用
对于开发者来说,协议扩展最常用于对现有协议的扩展,包括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(_:)
方法,可以把集合中下标能与x整除的元素去除,并返回处理后的集合。
由于CollectionType
是一个被arrays
和dictionaries
遵循的协议,所以对CollectionType
进行扩展后,array
和dictionary
都可以使用skip(_:)
方法了!在playground中,增加如下代码:
let bunchaBirds: [Bird] = [UnladenSwallow.African,
UnladenSwallow.European,
UnladenSwallow.Unknown,
Penguin.init(name: "King Penguin"),
SwiftBird.init(version: 2.0),
FlappyBird.init(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)]
bunchaBirds.skip(3)
在此,我们定义了包含了多种鸟类的数组bunchaBirds
。由于CollectionType
协议扩展了skip(_:)
方法,因此我们可以调用skip(_:)
方法来对数组进行处理。
扩展你的协议
除了可以扩展Swift原生协议,给Swift原生协议定义默认实现也同样让人激动。
在playground中,把协议Bird
修改为:
protocol Bird: BolleanType {
由于遵循了BooleanType
协议,因此,在Bird
协议中需要添加一个boolValue
来表达bool值。这是不是意味着开发者必须在每个遵循Bird
协议的类型下都给boolValue
设计一个实现呢?
当然,你可以使用协议扩展的方式给boolValue
设计一个默认实现。在playground的Bird
下方增加如下代码:
extension BooleanType where Self: Bird {
var boolValue: Bool {
return self.canFly
}
}
这样扩展了协议之后,canFly
属性就可以充当遵循Bird
协议类型的boolValue了。
下面我们通过下述代码进行测试:
if UnladenSwallow.African {
print("I can fly!");
} else {
print("Guess I'll just sit here :[")
}
在此,你会看到I can fly!
的打印。但尤其需要注意的是,在代码中,你不过在if
语句后使用了类型,并未使用任何额外的判断语句!
协议扩展对Swift原生库的影响
到目前为止,相信你已经体会到协议扩展对开发者代码带来的扩展性。同时,协议扩展也給Swift原生库代码的编写带来了极大的扩展性。
通过遵循map
, reduce
, filter
协议,Swift可以提升原生库的函数式编程模板。这些方法在CollectionType
类型中得到不少体现,例如Array
类型:
["frog", "pants"].map{$0.length}.reduce(0) { $0 + $1 }
上述代码中,让array
调用map
方法,得到一个新的array
,然后让新array
调用reduce
方法,得到新array
中的元素累加和9.
在此,map
和reduce
方法都成为了Array
的原生方法。如果你按住Cmd
并点击map
,就可以看到map
方法是如何定义的。
在Swift 1.2中,你将看到:
// Swift 1.2
extension Array : _ArrayType {
/// Return an `Array` containning the results of calling
/// `transform(x)` on each element `x` of `self`
func map<U>(transform: (T) -> U) -> [U]
}
这里,map
方法被定义为Array
类型的扩展。但是,Swift的高阶函数并非只提供Array
使用,应该所有的CollectionType
都能够使用,那么在Swift 1.2中是如何实现的呢?
如果让Range
调用map
方法,你可以看到如下定义:
// 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]
}
至此,我们发现在Swift 1.2中,因为struct
不能成为子类,也不能包含公用实现,所以必须给所有CollectionType
都重新定义map
的实现。
这种方式不仅限制了Swift标准库,同时也限制了开发者对Swift中各种类型的使用方式。
下列函数形参为「遵循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没有协议扩展功能,上述代码实际上会引入编译错误。map
和reduce
只存在于一个具体的定义好的类型中,而非存在于抽象的CollectionType
中。
但是,在Swift 2.0 协议扩展的功能下,map
方法的定义变成了如下:
extension CollectionType {
/// Returns an `Array` containing the results of mapping `transform`
/// over `self`.
///
/// - Complexity: O(N).
@warn_unused_result
public func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}
虽然不能看到看到方法map
的源码 —— 至少等到Swift 2正式发布将会是开源的!现在,CollectionType
有了一个默认的map
实现,所有遵循CollectionType
协议的类型都将支持map
方法。
接下来,在playground中添加如下代码:
func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(c : T) -> Double {
return c.map({$0.airspeedVelocity}).reduce(0) { max($0, $1) }
}
上述函数中,map
和reduce
方法均能被遵循CollectionType
协议的类型调用。现在,通过下述代码,即可算出「哪种鸟类飞得最快」了:
let flyingBirds: [Flyable] =
[UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird.init(version: 2.0)]
topSpeed(flyingBirds)
接下来应该做什么
首先,你可以下载本篇教程中完整的playground代码。
通过本教程,相信你已经体会到「协议式编程」的威力。你可以自己定义简单的协议然后在使用时根据需要去扩展它,同时,你也可以给先有的协议编写默认的实现,类似于基类但不同于基类的,协议还能够支持structs和enums。
再者,协议扩展不仅可以扩展你自己写的协议,同时也可以扩展Swift标准库里面的协议,Cocoa及Cocoa Touch的协议。
如果想知道Swift 2中还有什么新功能,你可以通过我们的文章「what’s new in Swift 2.0」或者苹果官方博客「Apple’s Swift blog」中查看。
同时,你可以通过观看WWDC的「Protocol Oriented Programming」视频,深入了解「协议式编程」背后的理论。
如果有任何问题?欢迎留言讨论!