动态调度协议扩展不适用于多个目标

时间:2018-11-26 19:14:43

标签: swift

这是我主要目标中的代码(不是测试目标):

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()
    }
}

它崩溃了。为什么不使用动态调度机制? MyClassdontCrash()的自己的实现,我希望它能触发。

1 个答案:

答案 0 :(得分:1)

您的Project模块声明MyClass符合ProtocolA

Swift使用称为“协议见证表”的数据结构实现该一致性。对于协议所声明的每个方法,见证表都包含一个函数,该函数为符合类型的方法调用该方法的实际实现。

具体来说,有一个见证表,用于证明MyClassProtocolA的一致性。该见证人表包含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导入)。