Swift协议扩展方法调度

今天,我想写一个我发现的错误。 当我接触CoreValue时,我正在研究它。 CoreValue是一个框架,允许您使用CoreData保留值类型。 该错误导致数据库中的条目重复

该错误也很有趣,因为我学到了一些东西。 它使用语言功能作为隐藏的斗篷。 一旦找到了怪物,我就能看到这种语言特征并对其进行了解。 然后,我决定写这则短篇小说。

我学到了什么?

  1. 协议扩展有2种方法。 需求 方法静态 方法“没有需求支持”)。
  2. 需求方法使用动态调度 。 静态方法使用静态调度
  3. 知道它们之间的差异非常重要,这样您就不会产生错误。

我将通过首先解释该错误来说明为什么它很重要。 您可以跳过此部分,并了解以下每种方法。

我将不使用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