Swift协议扩展方法调度
今天,我想写一个我发现的错误。 当我接触CoreValue时,我正在研究它。 CoreValue是一个框架,允许您使用CoreData保留值类型。 该错误导致数据库中的条目重复 。
该错误也很有趣,因为我学到了一些东西。 它使用语言功能作为隐藏的斗篷。 一旦找到了怪物,我就能看到这种语言特征并对其进行了解。 然后,我决定写这则短篇小说。
我学到了什么?
- 协议扩展有2种方法。 需求 方法和静态 方法 ( “没有需求支持”)。
- 需求方法使用动态调度 。 静态方法使用静态调度 。
- 知道它们之间的差异非常重要,这样您就不会产生错误。
我将通过首先解释该错误来说明为什么它很重要。 您可以跳过此部分,并了解以下每种方法。
我将不使用CoreValue示例,因为它很难解释。 所以,我要编一个故事。
英雄中隐藏的错误
这是一个关于不太聪明的英雄的故事。 英雄就是战士。 和战士,他们只是fight().
那是必要条件 。 战斗人员在战斗时只会掷punch()
。 这是用扩展方法编码的,因为它是所有Fighter的共同行为:
协议战斗机{
func fight() //要求
}
扩展战斗机{
//需求
func fight(){
self.punch()
}
//静态方法(协议中不需要)
funcpunch(){
打印( “打拳” )
}
}
英雄不是简单的战士,他们是MagicalFighters。 当魔法战士猛击时,他们首先施法:
协议MagicalFighter:战斗机{
func castSpell() //要求
}
扩展MagicalFighter {
//需求
func castSpell(){
打印( “ MagicalFighter施放了一个咒语” )
}
//静态的
funcpunch(){
self.castSpell()
打印( “魔术战斗机拳” )
}
}
还有一个细节,英雄以自己的方式施放咒语:
struct Hero:MagicalFighter {
func castSpell(){
打印(“ 英雄使用特殊咒语 ”)
}
}
最后,一个英雄被告知要战斗:
让gordo = Hero()
gordo.fight()
戈多打架时会发生什么? 他是英雄,因此是魔术战士。 因此,他应该在掷拳之前施放特殊的咒语。 至少那是我的想法。 但是事实并非如此,存在一个隐藏和弄乱gordo的bug。
打印什么,为什么?
答案是“打拳”。 原因是punch()
没有需求支持。 这是一种静态方法。 静态方法使用静态调度。
等一下 什么是静态方法? 什么是静态调度?
方法派遣
这是选择在调用方法时将执行什么实现的方法。 当编译器在编译时解决该问题时,它就是静态调度。 在运行时解决该问题时,它就是动态调度。
动态调度。
如果您习惯了OOP,类和继承,则对动态调度很熟悉。 这是选择方法实现的方式,这为多态性铺平了道路。 这就是为什么我认为戈尔多会施展特殊咒语的原因。 因为我习惯于动态调度和多态。
当您将消息发送到类实例时,动态调度将从层次结构中选择实现。 在运行时选择实现。 Objective-C使用此机制。 除了静态调度外,Swift也使用它。
静态调度
静态分派在编译时解决问题。 在Swift中,值类型使用静态分派,因为它们不需要继承。 全局函数,外部类,结构或枚举也使用静态分派。
静态分派可提高性能,从而降低编程中的动态行为/语义。 是否想利用此优势并提高性能? 阅读Apple的这篇文章,并学习一两个技巧,以避免动态调度。
Swift有3种方法分派。 直接或静态,见证表分派和消息分派。 您是否想进一步了解Swift中的方法分派? 阅读本文。
要求方法
需求方法是您在协议定义中声明的方法。 符合协议的类型将为每个需求定义一个实现。 除非扩展中有通用的实现,否则它是强制性的。
协议扩展可以定义需求的通用实现。 然后,所有符合协议的类型都将具有默认实现。 您可以决定以自己的类型提供自己的实现。 这就是他们所谓的定制点 。 就像Hero中的castSpell()
一样。
需求方法使用动态调度 。 这就是为什么符合类型具有自定义需求的特权的原因。 如果符合协议,则可以为所需方法定义实现。 该实现将被使用。 如果扩展中存在通用实现,则将其丢弃。
静态方法
在协议扩展中,我使用“ 静态”一词来引用这些协议不需要的方法。 在传统的static func
意义上,它们不是静态的。 我不是在讨论像static func doSomething()
这样的结构中的类型级别方法。 它们是静态的,因为它们使用静态分派。 在英雄示例中, punch()
是Fighter扩展中的静态方法
戴夫·亚伯拉罕(Dave Abrahams)在他著名的演讲“面向协议的编程 ”中将它们称为“没有需求方法的支持” 。 这是那一刻的链接。 在尝试解释所有这些内容时,使用该术语会造成混淆。 这就是为什么我改用“ 静态”的原因。
静态方法使用静态分派。 因此,符合类型没有特权对其进行自定义。 如果您遵守协议,则可以为实现提供静态方法的相同签名。 没关系。 唯一使用的实现将是扩展中的实现。
那么,这到底意味着什么呢?
静态方法没有多态性。 无论您在扩展中定义什么,如果不是必需的,它都是静态的。 因此,请忘记这些方法的多态性。 您的符合类型可以为其实现,但这并不重要。
回到例子
现在,我将以“英雄”示例开始我的工作。 将Fighter定义为punch()
。 然后,您将看到gordo做了他打算做的事情。 他将打印:
“英雄施放特殊法术”“魔法战斗机拳”
您可以进行以下另一项更改以查看会发生什么。 在MagicalFighter扩展内提供相同的需求方法fight()
。 该实现将调用MagicalFighter内部的静态方法punch()
。 然后,它将调用静态方法castSpell()
,显示“ MagicalFighter投射了一个咒语”。
最后的话
静态分派产生更快的代码。 当您根据OOP进行思考和编码时,动态调度可提供灵活性。 通常,Swift将动态分配用于纯OOP事物。 像类,协议和闭包一样。 对于值类型,使用静态分派。
我认为了解静态和动态调度之间的区别很重要。 同样,当Swift选择一个而不是另一个时。 不仅因为您可以编写更好的代码并提高性能。 但是,因为如果您不知道自己在做什么,很容易引入错误。
操场
这是一个带有操场代码的要点。 在这里和那里取消注释,并查看结果更改。
参考文献:
Swift中的方法调度— RaizException — Raizlabs开发人员博客
方法调度是程序在调用方法时如何选择要执行的指令的方法。 就是这样…… www.raizlabs.com 凯文·巴拉德(sevift-evolution)提案:方法调用的通用动态调度
您认为Swift更喜欢虚拟调度。 我认为它更喜欢静态的。 我认为这里真正发生的是…… lists.swift.org 克里斯·拉特纳(Chris Lattner)对凯文·巴拉德(Kevin Ballard)的回应。
2015年12月11日晚上8:56,凯文·巴拉德(Kevin Ballard)通过swift-evolution写道:>>您认为Swift偏爱虚拟调度… list.swift.org 协议扩展中的方法调度— Ole Begemann
Swift对协议扩展中的方法使用两种不同的调度机制。 凯文·巴拉德(Kevin Ballard)解释了为什么会这样。 oleb.net 通过减少动态调度来提高性能– Swift博客
从创建它的工程师那里获取有关Swift编程语言的最新新闻和有用的提示。 developer.apple.com 骗巫师巨魔
也是布鲁协会的成员。 www.sketchport.com