这是我主要目标中的代码(不是测试目标):
protocol ProtocolA {
func dontCrash()
}
extension ProtocolA {
func dontCrash() {
fatalError()
}
func tryCrash() {
dontCrash()
}
}
class MyClass: ProtocolA {}
在测试目标(如此不同的目标)中,我得到了以下代码:
import XCTest
@testable import Project
extension MyClass {
func dontCrash() {
print("I dont crash")
}
}
class ProjectTests: XCTestCase {
func testExample() {
MyClass().tryCrash()
}
}
它崩溃了。为什么不使用动态调度机制? MyClass
是dontCrash()
的自己的实现,我希望它能触发。
答案 0 :(得分:1)
您的Project
模块声明MyClass
符合ProtocolA
。
Swift使用称为“协议见证表”的数据结构实现该一致性。对于协议所声明的每个方法,见证表都包含一个函数,该函数为符合类型的方法调用该方法的实际实现。
具体来说,有一个见证表,用于证明MyClass
与ProtocolA
的一致性。该见证人表包含dontCrash
声明的ProtocolA
方法的函数。见证表中的该函数调用MyClass
dontCrash
方法。
当您的测试用例命中fatalError
时,您可以从堆栈跟踪的协议见证表中看到该功能:
#8 0x00000001003ab9d9 in _assertionFailure(_:_:file:line:flags:) ()
#9 0x00000001000016fc in ProtocolA.dontCrash() at /Users/rmayoff/TestProjects/Project/Project/AppDelegate.swift:11
#10 0x0000000100001868 in protocol witness for ProtocolA.dontCrash() in conformance MyClass ()
#11 0x000000010000171e in ProtocolA.tryCrash() at /Users/rmayoff/TestProjects/Project/Project/AppDelegate.swift:15
#12 0x00000001030f1987 in ProjectTests.testExample() at /Users/rmayoff/TestProjects/Project/ProjectTests/ProjectTests.swift:12
#13 0x00000001030f19c4 in @objc ProjectTests.testExample() ()
第10帧是从tryCrash
到协议见证表中函数的调用。第9帧是从协议见证表功能到dontCrash
的实际实现的调用。
Swift会在声明一致性的模块中发出协议见证表。因此,在您的情况下,见证表是Project
模块的一部分。
您对测试包中dontCrash
的覆盖不能更改见证表的内容。为时已晚。 Swift生成Project
模块时,见证人表已完全定义。
这就是为什么必须这样:
假设我是Project
模块的作者,而您只是该模块的用户。当我编写Project
模块时,我知道调用MyClass().dontCrash()
会调用fatalError
,因此我依靠这种行为。在Project
内部的许多地方,我打电话给MyClass().dontCrash()
是因为我知道它将叫fatalError
。您作为Project
的用户,不知道有多少Project
取决于该行为。
现在,您在应用中使用了Project
模块,但您将MyClass().dontCrash()
追溯更改为不调用fatalError
。现在,所有Project
调用MyClass().dontCrash()
的地方都不像我编写Project
模块时所期望的那样。即使您没有更改Project
模块或Project
导入的任何模块的源代码,也已经破坏了Project
模块。
这对于Project
模块的正确操作至关重要。因此,更改MyClass().dontCrash()
含义的唯一方法(从Project
模块内部调用时)是更改Project
模块本身的源代码(或更改某些Project
导入)。