在Xcode 7中更改+加载方法的顺序

我发现Xcode 7(版本7.0(7A220))在unit testing中改变了调用类和类的+load方法的顺序。

如果属于testing目标的类别实现了一个+load方法,那么现在在最后调用该类时,该类的实例可能已经被创build和使用。

我有一个AppDelegate ,它实现+load方法。 AppDelegate.m文件还包含AppDelegate (MainModule)类别。 此外,还有一个unit testing文件LoadMethodTestTests.m ,它包含另一个类别 – AppDelegate (UnitTest)

这两个类别也实现+load方法。 第一类属于主要目标,第二类属于testing目标。

我做了一个小testing项目来演示这个问题。 这是一个空的默认Xcode一个视图项目,只有两个文件被更改。

AppDelegate.m:

 #import "AppDelegate.h" @implementation AppDelegate +(void)load { NSLog(@"Class load"); } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"didFinishLaunchingWithOptions"); return YES; } @end @interface AppDelegate (MainModule) @end @implementation AppDelegate (MainModule) +(void)load { NSLog(@"Main Module +load"); } @end 

和一个unit testing文件(LoadMethodTestTests.m):

 #import <UIKit/UIKit.h> #import <XCTest/XCTest.h> #import "AppDelegate.h" @interface LoadMethodTestTests : XCTestCase @end @interface AppDelegate (UnitTest) @end @implementation AppDelegate (UnitTest) +(void)load { NSLog(@"Unit Test +load"); } @end @implementation LoadMethodTestTests -(void)testEmptyTest { XCTAssert(YES); } @end 

testing

我在Xcode 6/7上执行了这个项目的unit testing(代码和github链接如下),并获得了以下+load调用顺序:

 Xcode 6 (iOS 8.4 simulator): Unit Test +load Class load Main Module +load didFinishLaunchingWithOptions Xcode 7 (iOS 9 simulator): Class load Main Module +load didFinishLaunchingWithOptions Unit Test +load Xcode 7 (iOS 8.4 simulator): Class load Main Module +load didFinishLaunchingWithOptions Unit Test +load 

AppDelegate已经被创build之后,Xcode 7最后运行testing目标类别+load方法( Unit Test +load )。 这是一个正确的行为还是应该发送给苹果的错误?

可能是没有指定,所以编译器/运行时是自由重新安排调用? 我看了这个问题以及NSObject文档中的+ load描述,但是我不太明白当类别属于另一个目标时+load方法应该如何工作。

或者可能由于某种原因AppDelegate是某种特殊情况?

我为什么这么问

  1. 教育目的。
  2. 我曾经在unit testing目标内的一个类别中执行方法debugging。 现在,当调用顺序发生变化时,在调整之前执行applicationDidFinishLaunchingWithOptions 。 我相信还有其他方法可以做到这一点,但对Xcode 7来说,这似乎是违反直觉的。我认为,当一个类被加载到内存中时, +load这个类并+load所有的方法它的类别应该被调用之前,我们可以与这个类的东西(如创build一个实例,并调用didFinishLaunching... )。

TL,DR:这是xctest的错,不是objc的。

这是因为xctest可执行文件(实际运行unit testing的可执行文件,位于$XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest加载了它的包。

Pre-Xcode 7在运行任何testing之前加载了所有引用的testing包。 这可以看出(对于那些关心),通过反汇编Xcode 6.4的二进制文件,可以看到符号-[XCTestTool runTestFromBundle:]的相关部分。

在Xcode 7版本的xctest ,你可以看到它延迟了testing包的加载,直到实际的testing由XCTestSuite运行,在实际的XCTest框架中,可以在符号__XCTestMain看到,该符号仅在testing主机应用程序正在build立。

因为这些被调用的内部顺序发生了变化,所以调用你的testing的+load方法的方式是不同的。 没有对Objective-c-runtime的内部进行修改。

如果你想在你的应用程序中解决这个问题,你可以做一些事情。 首先,你可以使用+[NSBundle bundleWithPath:]手动加载你的bundle,然后调用-load

你也可以将你的testing目标链接回你的testing主机应用程序(我希望你正在使用一个独立的testing主机,而不是你的主应用程序!),这会在xctest加载主机应用程序时自动加载。

我不认为它是一个错误,它只是XCTest的一个实现细节。

来源:只需花费最近3天拆解xctest一个完全不相关的理由。

Xcode 7在iOS模板项目中有两个不同的加载顺序。

unit testing用例。 对于unit testing,在应用程序启动到主屏幕之后,将testing包注入到正在运行的模拟中。 默认的unit testing执行顺序如下所示:

 Application: AppDelegate initialize() Application: AppDelegate init() Application: AppDelegate application(…didFinishLaunchingWithOptions…) Application: ViewController viewDidLoad() Application: ViewController viewWillAppear() Application: AppDelegate applicationDidBecomeActive(…) Application: ViewController viewDidAppear() Unit Test: setup() Unit Test: testExample() 

UItesting用例。 对于UItesting, 单独的第二个进程 XCTRunner被设置,它执行被testing的应用程序。 一个参数可以从testingsetUp()传递…

 class Launch_UITests: XCTestCase { override func setUp() { // … other code … let app = XCUIApplication() app.launchArguments = ["UI_TESTING_MODE"] app.launch() // … other code … } 

被收到的AppDelegate

 class AppDelegate: UIResponder, UIApplicationDelegate { func application(… didFinishLaunchingWithOptions… ) -> Bool { // … other code … let args = NSProcessInfo.processInfo().arguments if args.contains("UI_TESTING_MODE") { print("FOUND: UI_TESTING_MODE") } // … other code … 

可以通过从testing代码和应用程​​序代码中打印NSProcessInfo.processInfo().processIdentifierNSProcessInfo.processInfo().processName in来观察对单独进程注入