OCMock 3 部分模拟:Class 方法和 objc 运行时

OCMock 3 Partial Mock: Class methods and the objc runtime

我 运行 在使用 OCMock 3 部分模拟定义 class 方法的对象时遇到了一个有趣的问题。我不确定这是作为部分模拟参与的动态 subclassing 的问题还是我对 objc 运行时的误解。任何帮助将不胜感激。

作为 运行 测试和其他调试构建的一部分,我们使用 OmniFoundations 的 OBRuntimeCheck 对方法声明进行一些运行时验证。简而言之,其中一项检查尝试使用运行时来验证类型签名是否与 class 方法在继承和协议一致性方面匹配。这是通过列出在运行时注册的 classes 来实现的,并且对于每个 class,metaClass 的实例方法被复制。对于元类中的每个 Method,如果它存在于元类的超类class中,则比较类型签名。

当为 ocmock 替换选择器之一 ocmock_replaced_* 在元类的 superclass 上调用 class_getInstanceMethod 时,问题就出现了。测试崩溃 EXC_BAD_INSTRUCTION code=EXC_i386_INVOP subcode=0x0 并且 no class for metaclass 记录在控制台中。给出的例子:

class_getInstanceMethod(metaSuperClass, NSSelectorFromString(@"ocmock_replaced_classMessage")) 

当部分模拟一个定义 class 方法的对象时,OCMock 3 框架似乎生成了一个动态子class,对模拟对象进行了一些 isa 调配,还有一些 isa 调配动态生成的 class' metaClass.

这种行为和崩溃是 OCMock 3 中的新功能,我真的不知道下一步该往哪里看。任何运行时专家都知道这里可能发生什么?在查看代码时,我确实感到惊讶的是,用于模拟的动态生成的 class 将其元 class 调出,但我不一定认为这是错误的。为了便于调试,我在 OCMock 的新分支中创建了一个简化的测试用例。崩溃测试可以找到here。任何指导帮助将不胜感激。

我可能跑题了,但我认为 metaClass 的 superclass 是 NSObject(这就是为什么你通常可以在 class 对象上调用 NSObject 实例方法).我不确定您是否应该使用 metaClass.

的 superclass 通常做任何事情

一般来说,metaClass 存储了关于 class 方法的所有信息。因此,在 metaClass 上获取 "instance" 方法与在关联的正则 Class 上获取 class 方法相同。运行时可以简单地解引用实例的"isa"指针来查找方法列表来查找实例方法;在 Class 对象上执行相同的操作会得到元 class(具有相同的结构),因此相同的过程会导致找到 class 方法。

OCMock 将为任何部分 mock 创建一个神奇的 subclass,并将该实例上的 class 更改为新的 subclass,因此所有实例方法调配都将是具体到那个实例。但是,对于 class 方法,我认为它必须修改原始 class 本身——否则,将不会拦截在常规代码中对常规 class 方法的调用。它保留原始实现的副本,以便当您在 mock 上调用 -stopMocking 时,它可以恢复原始实现(添加的 ocmock_replaced* impl 仍将存在,但不应再被调用)。

您可以简单地忽略任何以 "ocmock_replaced" 开头的选择器,因为它确实与您可能正在检查的实际代码无关。将 "class_getInstanceMethod(metaSuperClass, ..." 更改为 "class_getClassMethod(regularSuperClass, ..." 可能也会有更好的运气。我不确定为什么你会遇到崩溃 - 我希望 class_getInstanceMethod(metaSuperClass, ...) 在大多数情况下只是 return NULL。