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

Fre*_*ame 18 unit-testing objective-c objective-c-runtime ios xcode7

我发现Xcode 7(版本7.0(7A220))改变了+load在单元测试期间调用类和类别的方法的顺序.

如果属于测试目标的类别实现了一个+load方法,那么现在可以在最后调用它,此时类的实例可能已经被创建和使用.

我有一个AppDelegate实现+load方法.该AppDelegate.m文件还包含AppDelegate (MainModule)类别.此外,还有一个单元测试文件LoadMethodTestTests.m,其中包含另一个类别 - AppDelegate (UnitTest).

这两个类别也实现了+load方法.第一类属于主要目标,第二类属于测试目标.

我做了一个小测试项目来证明这个问题.它是一个空的默认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
Run Code Online (Sandbox Code Playgroud)

和一个单元测试文件(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
Run Code Online (Sandbox Code Playgroud)

测试

我在Xcode 6/7上执行了这个项目的单元测试(代码和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
Run Code Online (Sandbox Code Playgroud)

Xcode 7 在已经创建之后最终运行测试目标类别+load方法(Unit Test +load)AppDelegate. 这是一个正确的行为还是应该发送给Apple的错误?

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

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

我为什么这么问

  1. 教育目的.
  2. 我曾经在单元测试目标内的类别中执行方法调配.现在,当呼叫顺序发生变化时,applicationDidFinishLaunchingWithOptions在发生混合之前执行.我相信还有其他方法可以做到这一点,但它对我来说在Xcode 7中的工作方式似乎是违反直觉的.我认为当一个类被加载到内存中时,+load这个类+load的所有类别的方法都被认为是在我们可以使用这个类之前调用​​(比如创建一个实例和调用didFinishLaunching...).

Ric*_*III 21

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

这是因为xctest可执行文件(实际运行单元的那个,在$XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest加载它的bundle时).

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

在Xcode 7版本中xctest,您可以看到它延迟了测试包的加载,直到XCTestSuite在实际XCTest框架中运行实际测试,这可以在符号中看到,该符号__XCTestMain仅在测试的主机应用程序设置之后被调用 -起来.

由于内部调用的顺序发生了变化,因此+load调用测试方法的方式也不同.对objective-c-runtime的内部结构没有任何更改.

如果要在应用程序中修复此问题,可以执行一些操作.首先,您可以使用手动加载捆绑包+[NSBundle bundleWithPath:],然后调用-load它.

您还可以将测试目标链接回测试主机应用程序(我希望您使用的是单独的测试主机而不是主应用程序!),这将使其在xctest加载主机应用程序时自动加载.

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

资料来源:过去3天只需花费xctest一个完全不相关的原因进行拆解.