In our testing framework (based on Kiwi, which in turn is based on XCTest), we're using the "swizzling on load" technique described on NSHipster here to switch some stuff with mocks on load. It has been working well enough until we upgraded to XCode 7, and now somehow +load
methods are called twice. As far as I understand this should never happen no matter what?
Here's the stack trace of the first +load call (takes place before 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
And this is how the second callstack looks like:
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
It looks like the dynamic loader initially calls the +load
methods as expected, but then the XCTest runtime calls them again.
The thing is, even dispatch_once
doesn't work as static variables don't seem to be initiated properly so the dispatch_once_t
tokens change between +load
invocations! The only thing that worked was creating a C++ class and delegating the dispatch_once
call to it (using a proper c++ static variable for the dispatch_once_t
).
EDIT - I'm pretty sure this +load
order change is related, but I don't see how changing the order would have caused it to be run twice.
EDIT2 - It seems that this behavior is not new. From a comment in a related blog post:
If you are running a test bundle that is injected into an app, and both the app and the test bundle link to the same .a file, any load method in that .a file will be triggered twice.