注意:这套教程要求Xcode 7、Swift 2,准备好了这个环境,下面的教程才能进行下去。你能够在 Apple’s developer portal 下载最新的beta版本。
在WWDC 2015上,Apple发布Swift语言的第二个修订版本–Swift 2,它包含了许多新的语言特性,这些特性能够帮助你更加方便得写代码。
在这些新特性中,最令我兴奋的是 protocol extensions 。在第一个版本的Swift中,它能够扩展class、struct和enum类型的功能。现在,在Swift 2中,你也能扩展一个协议。
首先,它可能听起来像是一个不太重要的特性,但是协议扩展(protocol extensions)它的确十分强大,并且能够改变你写代码的方式。在这篇教程中,你能探索创建和使用协议扩展的方式,而且它作为一个新的技术和面向协议编程的模式,将会开始出现在你的代码中。
你也能看到,Swift团队怎样使用协议扩展去增强他们自己的Swift的标准库的,并且看到它将会怎样影响你写代码的。
Getting Started
首先新建一个playground。在Xcode中,选择 File\New\Playground… ,然后命名这个playground为 SwiftProtocols。你能选择任何平台,因为在本教程中的所有得代码都是平台无关的。点击 Next ,选择保存路径,最后点击 Create。
创建好了playground后,添加如下代码:
protocol Bird { var name: String { get } var canFly: Bool { get }}protocol Flyable { var airspeedVelocity: Double { get }}
这个定义了一个简单得 Bird 协议,其有一个属性 name 和 canFly ,同样 Flyable 协议定义了一个 airspeedVelocity属性。
在原先对协议的理解中,你可能已经开始使用Flayable作为一个基本类型了,并且通过继承定义Bird和其他能够飞的东西,例如飞机。但是现在,everything将开始作为协议出现。(原文:In a pre-protocol world, you might have started with Flyable as a base class and then relied on inheritance to define Bird as well as other things that fly, such as airplanes. Note that here, everything is starting out as a protocol!)
接下来,在你定义一个真实类型的时候,你将看到通过协议扩展怎样使你的整个系统更加的灵活。
Defining Protocol-conforming Types 定义一个遵守协议的类型
在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 通过flappyFrequency 和 flappyAmplitude 计算获得的。因为能够飞,所以它的 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 // Swift is FAST! var airspeedVelocity: Double { return 2000.0 }}
Penguin 是 Bird ,但是不能飞。你不能继承所有得特性,毕竟不能使所有得鸟会飞。 SwiftBird 确实有很快得速度。
你已经看到一些冗余的地方,每个Bird类型,都必须声明它是否canFly,即使你已经在你的系统中表明它遵守Flyalbe协议了。
Extending Protocols With Default Implementations 使用默认实现扩展协议
使用协议扩展,你能够为协议定义默认的行为。在 Brid 定义的下面添加如下的代码:
extension Bird where Self: Flyable { // Flyable birds can fly! var canFly: Bool { return true }}
这个定义了 Bird 的协议扩展,当类型也是 Flyable 的时候,使 canFly 的默认行为是返回true。换句话说,任意的Flyable bird不需要明确的声明 canFly 属性了。
Swift 1.2中介绍了在if-let绑定中的where的语法,在Swift 2使其更加的强大————把这个能力扩展到了协议上。
从 FlappyBird 和 SwiftBird 结构体声明中删除了 let canFly = true 。你能够看到playground能够正常得编译了,由于协议扩展现在为你处理了其他得必要的工作。
Why Not Base Classes?
协议扩展和默认实现,可能看起来和其他语言中使用基类或者虚类相似,但是在Swift中,它提供了一些关键的有点。
由于类型能够实现多于一个协议,他们能够被多个不同协议的默认实现装饰。与其他语言中的类的多重继承不同,协议扩展不去声明任何附加的状态。
协议能够被classes、structs和enums遵守。基类和继承只能限制在类上使用。
换句话说,协议扩展为值类型和类提供了一种定义默认行为的能力。
你已经在struct中看到这些了。接下来,在playground的末尾添加如下的enum的定义。
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!") } }}
对于其他值类型,你需要做的是定义正确的属性,类似 UnladenSwallow 遵守两个协议一样。因为她遵守 Bird 和Flyable 两个协议,所以它也获得canFly的默认行为。
Did you really think this tutorial involving airspeedVelocity wouldn’t include a Monty Python reference? :](译者注:不清楚怎么翻译)
Extending Protocols 扩展协议
也许使用协议扩展最普通的就是扩展现在的协议,在Swift中这些定义不仅存在于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++ } while (index != self.endIndex) return result }}
这个在 CollectionType 协议上定义了一个扩展,在其中定义了一个**skip(_:)方法,该方法跳过每个是skip(译者注:传递进入方法的参数)整数倍位置的元素,并且返回没有跳过元素的数组。
在Swift中, CollectionType 是一个呗arrays和dictionaries遵守的协议。这也就意味着,在你的app中这个新的行为已经在你的每个 CollectionType 上存在了。在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(3)
这里,你定义了一个bird数组,包含你定义的多种类型。由于arrays遵守 CollectionType 协议,意味着 skip(_:) 在它上面可以使用。
Extending Your Own Protocols 扩展你自己的协议
与向Swift标准库的协议添加新的行为让你很兴奋了一样,你也能定义默认的行为。
修改 Bird 协议的声明,使其遵守 BooleanType 协议:
protocol Bird: BooleanType {
遵守 BooleanType 协议,意味着你的类型需要有一个 boolValue 属性,它是Boolean类型的。这个以为你现在必须为每个 Bird 类型添加这个属性吗?
当然不用,使用协议扩展就是一种简单得方式。在 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 :[")}
Effects on the Swift Standard Library 在Swift标准库上的作用
你已经看到了协议扩展是定制和扩展你APP代码的一个强大的方式。当你看到Swift团队怎样使用协议扩展增强Swift标准库,你可能会更惊奇。
Swift有些地方有函数式编程的影子,例如标准库中的 map 、 reduce 和 filter 。这些方法存在于各个 CollectionType的方法中,例如 Array 。
// Counts the number of characters in the array["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // returns 9
在array上调用 map ,然后返回一个新的array,在新的数组上调用 reduce ,返回最后的值9。
译者注:reduce(sequence, initial, combineClosure): 从第一个初始值开始对其进行combineClosure操作,递归式地将序列中的元素合并为一个元素。
在这里, map 和 reduce 作为Swift标准库中的Array的方法。如果你在 map 上 Cmd-Click ,你能看到它是怎么定义的。
译者注:Cmd-Click 按住Command,点击鼠标左键。
在Swift 1.2中,你可能看到类似如下的定义:
// Swift 1.2extension 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 函数是在定义在一个函数的扩展中。然而Swift的功能函数应该不仅仅能够作用在 Array 上,也能作用在任何CollectionType 上,Swift 1.2是怎样使它这样的呢?
如果你在 Range 上调用 map 函数,在 map 上 Cmd-Click ,你将看到如下的定义:
// Swift 1.2extension 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中出现, map 需要在Swift标准库中的任何一中 CollectionType 中重新定义。这是因为虽然 Array 和Range 都是 CollectionType 类型的,但是structs不能被子类化、没有统一的实现。
这个并不是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 函数只是存在于Swift预定义的类型中,它并不会工作在任何 CollectionType 的子类型上。
在Swift 2.0中,存在协议扩展,在 Array 和 Range 类型上定义了 map 方法,如下:
// Swift 2.0extension 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 的源码,————至少在Swift 2开源以前看不到!———— CollectionType 现在有了一个 map的默认实现,并且在所有的 CollectionType 上都能使用。
在你的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 在你的 Flyable 实例的collection中是可以使用了。现在你能通过在playground的底部添加如下的代码,响应它们中谁最快的问题了:
let flyingBirds: [Flyable] = [UnladenSwallow.African, UnladenSwallow.European, SwiftBird(version: 2.0)]topSpeed(flyingBirds) // 2000.0
Where To Go From Here? 接下来去哪?
你能在 这里 下载本教程中的代码的playground。
通过创建你自己的简单的协议和使用协议扩展扩展它们。通过默认实现,你能给目前的协议提供通用的自动的行为,这个类似class,但是比它更好,因为它能够作用在结构体和枚举上。
此外,协议扩展不仅仅能够用于扩展你自己的协议,也能够扩展Swift标准库、Cocoa和Cocoa Touch中的协议,提供默认的实现。
为了对Swift 2的其他令人兴奋的特性有一个大概了解,你能够阅读 our “what’s new in Swift 2.0” article ,或者在 Apple’s Swift blog 查看Swift 2的介绍。
你能在苹果开发者门户网站中查看WWDC session Protocol Oriented Programming ,获取在它背后更深的原理。
有什么问题吗?在下面的讨论板块让我们知道吧!
原文链接:http://outofmemory.cn/swift/swift2-protocol-oriented-programing
共同学习,写下你的评论
评论加载中...
作者其他优质文章