XCode 7 XCTest (Kiwi) + методы категории нагрузки называются дважды

В нашей тестовой среде (на основе Kiwi, которая, в свою очередь, основана на XCTest), мы используем технику "swizzling on load", описанную в NSHipster здесь, чтобы переключить некоторые вещи с помощью mocks при загрузке. Он работает достаточно хорошо, пока мы не перешли на XCode 7, и теперь методы +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, [email protected]"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 invocations! Единственное, что сработало, это создать класс С++ и делегировать ему вызов dispatch_once (используя старую переменную С++ для dispatch_once_t).

EDIT - я уверен, что это +load изменение заказа связано, но я не вижу, как изменение порядка привело бы к его запуску в два раза.

EDIT2 - Кажется, что это поведение не нова. Из комментария в связанном сообщении в блоге:

Если вы используете тестовый пакет, который вводится в приложение, и как приложение, так и ссылка тестового пакета в тот же файл .a, любая загрузка метод в том, что файл .a будет запущен дважды.

Ответ 1

Похоже, что вы включили одно и то же расширение класса как в основном двоичном приложении приложения, так и в тестовом комплекте. Это также объясняет, почему вы видите отдельную установку статических токенов dispatch_once при втором загрузке.

Почему это может быть несколько вещей:

  • .mm файл включен в обе цели.
  • Xcode дважды загружает тестовый двоичный файл из-за изменений в тестовой процедуре
  • Оба тестовых целевых и прикладных ссылок на один и тот же файл .a, содержащий метод +load