XCode 7 XCTest(Kiwi) +加载类别方法调用了两次

Posted

技术标签:

【中文标题】XCode 7 XCTest(Kiwi) +加载类别方法调用了两次【英文标题】:XCode 7 XCTest(Kiwi) +load category methods called twice 【发布时间】:2015-10-15 17:20:21 【问题描述】:

在我们的测试框架(基于 Kiwi,而后者又基于 XCTest)中,我们使用 NSHipster here 中描述的“加载时混合”技术来切换一些加载时模拟的东西。在我们升级到 XCode 7 之前,它一直运行良好,现在不知何故 +load 方法被调用了两次。据我了解,无论如何这都不应该发生?

这是第一个 +load 调用的堆栈跟踪(发生在 main 之前):

 Foo`+[FooManager(self=FooManager, _cmd="load") load] + 149 at FooExtensions.mm:152
 libobjc.A.dylib`call_load_methods + 292
 libobjc.A.dylib`load_images + 129
 ...
 dyld`dyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*) + 1053
 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 202
 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 428
 dyld`_dyld_start + 71

这就是第二个调用堆栈的样子:

FooTests`+[FooManager(self=FooManager, _cmd="load") load] + 149 at FooExtensions.mm:152
ibobjc.A.dylib`call_load_methods + 292
ibobjc.A.dylib`load_images + 129

libdyld.dylib`dlopen + 70
CoreFoundation`_CFBundleDlfcnLoadBundle + 185
CoreFoundation`_CFBundleLoadExecutableAndReturnError + 336
Foundation`-[NSBundle loadAndReturnError:] + 641
XCTest`_XCTestMain + 542
IDEBundleInjection`____XCBundleInjection_block_invoke_2 + 20
CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 16
CoreFoundation`__CFRunLoopDoBlocks + 195
CoreFoundation`__CFRunLoopRun + 1016
CoreFoundation`CFRunLoopRunSpecific + 470
CoreFoundation`CFRunLoopRunInMode + 123
GraphicsServices`GSEventRunModal + 192
GraphicsServices`GSEventRun + 104
UIKit`UIApplicationMain + 160
Foo`UIApplicationMain(argc=<unavailable>, argv=<unavailable>, principalClassName=0x00000000, delegateClassName=@"AppDelegate") + 227 at ApplicationHooks.m:56
Foo`main(argc=5, argv=0xbfff7778) + 146 at main.mm:15
libdyld.dylib`start + 1

看起来动态加载程序最初按预期调用了+load 方法,但随后 XCTest 运行时再次调用它们。

问题是,即使dispatch_once 也不起作用,因为静态变量似乎没有正确启动,所以dispatch_once_t 标记在+load 调用之间发生变化!唯一可行的方法是创建一个 C++ 类并将 dispatch_once 调用委托给它(为 dispatch_once_t 使用适当的 c++ 静态变量)。

编辑 - 我很确定这个 +load order change 是相关的,但我看不出更改顺序会如何导致它运行两次。

EDIT2 - 这种行为似乎并不新鲜。来自related blog post 中的评论:

如果您正在运行注入到应用中的测试包,并且 应用程序和测试包都链接到同一个 .a 文件,任何负载 .a 文件中的方法将被触发两次。

【问题讨论】:

很确定你的包在加载调用之间被卸载了。尝试创建一个__attribute__((destructor)) 函数,看看是否正确。如果我的理论是正确的,您可以通过让您的测试目标不链接到具有该类别的框架,或者通过使用dlopen 和朋友进行运行时链接来解决它。 感谢您的快速回复!我实现了构造函数和析构函数,如下所述:***.com/q/2053029/67824 并在每个函数中触发了一个 NSLog。有趣的是,捆绑包从未卸载,但它确实加载了两次。鉴于此,我还应该尝试您的解决方法吗?如果是这样,我的测试目标需要具有该类别的库(就像我说我们正在使用它在+load 上使用模拟来调配一些东西)所以我想我应该走dlopen 路线? 顺便说一句,捆绑包被加载两次一定是一件坏事吗?否则,也许我可以在应用程序委托 (didFinishLaunchingWithOptions) 中进行调配? 【参考方案1】:

您似乎在主应用程序二进制文件和测试包中都包含了相同的类扩展。这也可以解释为什么您会在第二次加载时看到单独的静态 dispatch_once 令牌设置。

为什么会出现这种情况可能有很多原因:

.mm 文件包含在两个目标中 由于测试过程的变化,Xcode 两次加载测试二进制文件 测试目标和应用程序都链接到同一个 .a 文件,其中包含 +load 方法

【讨论】:

我认为原因比这更简单 - 我确实链接到包,包括应用程序和测试目标中的类扩展(请参阅我的上次编辑)。

以上是关于XCode 7 XCTest(Kiwi) +加载类别方法调用了两次的主要内容,如果未能解决你的问题,请参考以下文章

Xcode 5.0.2 dyld:库未加载:@rpath/XCTest.framework/Versions/A/XCTest

Xcode 7 中的 XCTest:如何验证视图中是不是存在某些文本

无法为“XCTest”加载底层模块

Xcode 5//iOS 6 和 7//TDD:OCUnit 或 XCTest

Xcode 7.3 XCTest of Disclosure Indicator in a custom TableViewCell

XCTest Xcode 7 beta 3 swift 2 从情节提要问题启动视图控制器