介绍
随着Swift 2的发布,Apple在Swift编程语言中添加了一系列新功能。 但是,最重要的一项是对协议进行全面检查。 Swift协议提供的改进功能允许一种新型的编程,即面向协议的编程。 这与我们许多人习惯的更为常见的面向对象的编程风格形成鲜明对比。
在本教程中,我将向您展示Swift中面向协议编程的基础知识以及它与面向对象编程的区别。
先决条件
本教程要求您正在运行Xcode 7或更高版本,其中包括对Swift编程语言的版本2的支持。
1.协议基础
如果您还不熟悉协议,则可以使用它们来扩展现有类或结构的功能。 可以将协议视为定义了一组属性和方法的蓝图或接口。 需要使用符合协议的类或结构来分别用值和实现填充这些属性和方法。
还应该注意,这些属性和方法中的任何一个都可以指定为可选的,这意味着实现它们不需要符合类型。 Swift中的协议定义和类一致性可能看起来像这样:
protocol Welcome {
var welcomeMessage: String { get set }
optional func welcome()
}
class Welcomer: Welcome {
var welcomeMessage = "Hello World!"
func welcome() {
print(welcomeMessage)
}
}
2.一个例子
首先,打开Xcode并为iOS或OS X创建一个新的Playground。一旦Xcode创建了Playground,请将其内容替换为以下内容:
protocol Drivable {
var topSpeed: Int { get }
}
protocol Reversible {
var reverseSpeed: Int { get }
}
protocol Transport {
var seatCount: Int { get }
}
我们定义了三个协议,每个协议包含一个属性。 接下来,我们创建一个符合这三个协议的结构。 将以下代码添加到游乐场:
struct Car: Drivable, Reversible, Transport {
var topSpeed = 150
var reverseSpeed = 20
var seatCount = 5
}
您可能已经注意到,我们没有创建符合这些协议的类,而是创建了一个结构。 我们这样做是为了避免面向对象编程固有的典型问题之一,即对象引用 。
例如,假设您有两个对象A和B。A自己创建一些数据并保留对该数据的引用。 然后,A通过引用与B共享此数据,这意味着两个对象都引用了同一对象。 在A不知道的情况下,B以某种方式更改了数据。
虽然这似乎不是一个大问题,但可能是A没想到数据会被更改。 对象A可能会找到不知道如何处理或处理的数据。 这是对象引用的常见风险。
在Swift中,结构是通过值而不是通过引用传递的。 这意味着,在以上示例中,如果将A创建的数据打包为结构而不是对象打包并与B共享,则将复制数据而不是通过引用共享。 然后,这将导致A和B都拥有它们自己相同数据的唯一副本。 B所做的更改不会影响A管理的副本。
将Drivable
, Reversible
和Transport
组件分解为单独的协议,与传统的类继承相比,还可以提供更高级别的自定义。 如果您已经阅读了有关 iOS 9中新GameplayKit框架的第一篇教程 ,那么这种面向协议的模型与GameplayKit框架中使用的Entities and Components结构非常相似。
通过采用这种方法,自定义数据类型可以从多个源而不是单个超类继承功能。 记住到目前为止的内容,我们可以创建以下类:
- 具有
Drivable
和Reversible
协议的组件的类 - 具有
Drivable
和Transportable
协议的组件的类 - 具有
Reversible
和可Transportable
协议的组件的类
对于面向对象的编程,创建这三个类的最合乎逻辑的方法是从一个包含所有三个协议的组件的超类继承。 但是,这种方法导致超类比需要的更加复杂,并且每个子类都继承了比其需要更多的功能。
3.协议扩展
自2014年发布以来,到目前为止,我向您展示的所有内容都可以在Swift中实现。这些相同的面向协议的概念甚至可以应用于Objective-C协议。 由于以前在协议上存在局限性,因此只有在第2版的Swift语言中添加了许多关键功能之后,才可能进行真正的面向协议的编程。其中最重要的功能之一是协议扩展 ,包括条件扩展 。
首先,让我们扩展Drivable
协议并添加一个函数,以确定特定的Drivable
是否比另一个Drivable
更快。 将以下内容添加到您的游乐场:
extension Drivable {
func isFasterThan(item: Drivable) -> Bool {
return self.topSpeed > item.topSpeed
}
}
let sedan = Car()
let sportsCar = Car(topSpeed: 250, reverseSpeed: 25, seatCount: 2)
sedan.isFasterThan(sportsCar)
您可以看到,执行操场的代码时,它输出的值为false
因为您的sedan
的默认topSpeed
为150
,小于sportsCar
。
您可能已经注意到,我们提供了函数定义而不是函数声明 。 这似乎很奇怪,因为协议仅应包含声明。 对? 这是Swift 2中默认行为的协议扩展的另一个非常重要的功能。 通过扩展协议,您可以为函数和计算的属性提供默认实现,从而不必遵守协议的类。
接下来,我们将定义另一个Drivable
协议扩展,但是这次我们只为也符合Reversible
协议的值类型定义它。 然后,此扩展程序将包含一个函数,该函数确定哪个对象具有更好的速度范围。 我们可以通过以下代码实现:
extension Drivable where Self: Reversible {
func hasLargerRangeThan(item: Self) -> Bool {
return (self.topSpeed + self.reverseSpeed) > (item.topSpeed + item.reverseSpeed)
}
}
sportsCar.hasLargerRangeThan(sedan)
Self
关键字(大写字母“ S”)用于表示符合协议的类或结构。 在上面的示例中, Self
关键字表示Car
结构。
运行操场的代码后,Xcode将在右侧的侧栏中输出结果,如下所示。 请注意, sportsCar
比sedan
距离更大。
4.使用Swift标准库
虽然定义和扩展自己的协议可能非常有用,但在使用Swift标准库时,协议扩展的真正功能就可以显示出来。 这使您可以向现有协议添加属性或函数,例如CollectionType
(用于数组和字典等)和Equatable
(能够确定两个对象何时相等)。 使用条件协议扩展,您还可以为符合协议的特定对象类型提供非常特定的功能。
在我们的操场上,我们将扩展CollectionType
协议并创建两种方法,一种用于获取Car
数组中汽车的平均最高速度,另一种用于平均倒车速度。 将以下代码添加到您的游乐场:
extension CollectionType where Self.Generator.Element: Drivable {
func averageTopSpeed() -> Int {
var total = 0, count = 0
for item in self {
total += item.topSpeed
count++
}
return (total/count)
}
}
func averageReverseSpeed<T: CollectionType where T.Generator.Element: Reversible>(items: T) -> Int {
var total = 0, count = 0
for item in items {
total += item.reverseSpeed
count++
}
return (total/count)
}
let cars = [Car(), sedan, sportsCar]
cars.averageTopSpeed()
averageReverseSpeed(cars)
定义averageTopSpeed
方法的协议扩展利用了Swift 2中的条件扩展。相比之下,我们直接在下面定义的averageReverseSpeed
函数是利用Swift泛型实现相似结果的另一种方法。 我个人更喜欢看上去更干净的CollectionType
协议扩展,但这取决于个人喜好。
在这两个函数中,我们遍历数组,将总数相加,然后返回平均值。 请注意,我们手动对数组中的项目进行计数,因为当使用CollectionType
而不是常规Array
类型的项目时, count
属性是Self.Index.Distance
类型的值,而不是Int
。
一旦操场执行完所有这些代码,您应该会看到输出平均最高速度183
和平均反向速度21
。
5.班级的重要性
尽管面向协议的编程是一种在Swift中管理代码的非常有效且可扩展的方式,但是在Swift中进行开发时仍然有使用类的完全合理的理由:
向后兼容
大多数iOS,watchOS和tvOS SDK都是使用面向对象的方法用Objective-C编写的。 如果需要与这些SDK中包含的任何API进行交互,则必须使用这些SDK中定义的类。
引用外部文件或项目
Swift编译器根据对象的使用时间和位置来优化它们的生命周期。 基于类的对象的稳定性意味着您对其他文件和项目的引用将保持一致。
对象引用
有时,对象引用正是您所需要的,例如,如果您要将信息馈入特定对象(例如图形渲染器)中。 在此类情况下,使用具有隐式共享的类非常重要,因为您需要确保将数据发送到的渲染器仍与以前的渲染器相同。
结论
希望在本教程结束时,您可以看到Swift中面向协议编程的潜力以及如何将其用于简化和扩展代码。 尽管这种新的编码方法不会完全替代面向对象的编程,但确实带来了许多非常有用的新可能性。
从默认行为到协议扩展,Swift中面向协议的编程将被许多未来的API所采用,并将完全改变我们对软件开发的思考方式。
与往常一样,请务必在下面的评论中留下您的评论和反馈。
翻译自: https://code.tutsplus.com/tutorials/protocol-oriented-programming-in-swift-2--cms-24979