动态调度协议扩展不适用于多个目标
Dynamic dispatching protocol extension doesn't work multiple targets
这是我在主要目标中的代码(所以不是测试目标):
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()
实现,我希望它能成功。
您的 Project
模块声明 MyClass
符合 ProtocolA
。
Swift 使用称为“协议见证 table” 的数据结构实现该一致性。对于协议声明的每个方法,witness table 包含一个调用符合类型的方法的实际实现的函数。
具体来说,MyClass
与ProtocolA
的一致性有一个见证table。 witness table 包含 ProtocolA
声明的 dontCrash
方法的函数。 witness table 中的那个函数调用 MyClass
dontCrash
方法。
当您的测试用例命中 fatalError
:
时,您可以在堆栈跟踪中看到来自协议见证 table 的函数
#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
到协议见证 table 中的函数的调用。帧 #9 是从协议见证 table 函数到 dontCrash
.
的实际实现的调用
Swift 在声明一致性的模块中发出协议见证 table。所以,在你的例子中,witness table 是 Project
模块的一部分。
您在测试包中覆盖 dontCrash
无法更改见证 table 的内容。为时已晚。当 Swift 生成 Project
模块时,witness table 被完全定义。
这就是为什么必须这样:
假设我是 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
进口)。
这是我在主要目标中的代码(所以不是测试目标):
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()
实现,我希望它能成功。
您的 Project
模块声明 MyClass
符合 ProtocolA
。
Swift 使用称为“协议见证 table” 的数据结构实现该一致性。对于协议声明的每个方法,witness table 包含一个调用符合类型的方法的实际实现的函数。
具体来说,MyClass
与ProtocolA
的一致性有一个见证table。 witness table 包含 ProtocolA
声明的 dontCrash
方法的函数。 witness table 中的那个函数调用 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
到协议见证 table 中的函数的调用。帧 #9 是从协议见证 table 函数到 dontCrash
.
Swift 在声明一致性的模块中发出协议见证 table。所以,在你的例子中,witness table 是 Project
模块的一部分。
您在测试包中覆盖 dontCrash
无法更改见证 table 的内容。为时已晚。当 Swift 生成 Project
模块时,witness table 被完全定义。
这就是为什么必须这样:
假设我是 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
进口)。