在 Xcode 7 中更改了 +load 方法顺序 [英] Changed +load method order in Xcode 7

查看:18
本文介绍了在 Xcode 7 中更改了 +load 方法顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现 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+(无效)负载{NSLog(@"类加载");}- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {NSLog(@"didFinishLaunchingWithOptions");返回是;}@结尾@interface AppDelegate (MainModule)@结尾@implementation AppDelegate (MainModule)+(无效)负载{NSLog(@"主模块+加载");}@结尾

还有一个单元测试文件(LoadMethodTestTests.m):

#import #import #import "AppDelegate.h"@interface LoadMethodTestTests : XCTestCase@结尾@interface AppDelegate (UnitTest)@结尾@implementation AppDelegate (UnitTest)+(无效)负载{NSLog(@"单元测试+负载");}@结尾@implementation LoadMethodTestTests-(void)testEmptyTest {XCTAssert(YES);}@结尾

测试

我在 Xcode 6/7 上对这个项目进行了单元测试(代码和 github 链接在下面)并得到以下 +load 调用顺序:

Xcode 6(iOS 8.4 模拟器):单元测试+负载类负载主模块+负载didFinishLaunchingWithOptionsXcode 7(iOS 9 模拟器):类负载主模块+负载didFinishLaunchingWithOptions单元测试+负载Xcode 7(iOS 8.4 模拟器):类负载主模块+负载didFinishLaunchingWithOptions单元测试+负载

问题

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

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

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

我为什么要问这个

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

解决方案

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

这是因为 xctest 可执行文件(实际运行单元测试的那个,位于 $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest 加载它的包.

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

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

因为这些被内部调用的顺序发生了变化,你的测试的 +load 方法被调用的方式是不同的.Objective-c-runtime 的内部没有任何变化.

如果您想在您的应用程序中修复此问题,您可以做一些事情.首先,您可以使用 +[NSBundle bundleWithPath:] 手动加载您的包,并在其上调用 -load.

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

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

来源:只是因为一个完全不相关的原因花了最后 3 天的时间来反汇编 xctest.

I found out that Xcode 7 (Version 7.0 (7A220)) changed the order in which +load methods for classes and categories are called during unit tests.

If a category belonging to the test target implements a +load method, it is now called at the end, when instances of the class might've already been created and used.

I have an AppDelegate, which implements +load method. The AppDelegate.m file also contains AppDelegate (MainModule) category. Additionally, there is a unit test file LoadMethodTestTests.m, which contains another category – AppDelegate (UnitTest).

Both categories also implement +load method. The first category belongs to the main target, the second one – to the test target.

Code

I made a small test project to demonstrate the issue. It is an empty default Xcode one view project with only two files changed.

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

And a unit test file (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

I performed Unit Testing of this project (the code and the github link are below) on Xcode 6/7 and got the following +load calls order:

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

Question

Xcode 7 runs the test target category +load method (Unit Test +load) in the end, after the AppDelegate has already been created. Is it a correct behavior or is it a bug that should be sent to Apple?

May be it is not specified, so the compiler/runtime is free to rearrange calls? I had a look at this SO question as well as on the +load description in the NSObject documentation but I didn't quite understand how the +load method is supposed to work when the category belongs to another target.

Or may be AppDelegate is some sort of a special case for some reason?

Why I'm asking this

  1. Educational purposes.
  2. I used to perform method swizzling in a category inside unit test target. Now, when the call order has changed, applicationDidFinishLaunchingWithOptions is performed before the swizzling takes place. There are other ways to do it, I believe, but it just seems counter-intuitive to me the way it works in Xcode 7. I thought that when a class is loaded into memory, +load of this class and +load methods of all its categories are supposed to be called before we can something with this class (like create an instance and call didFinishLaunching...).

解决方案

TL,DR: It's xctest's fault, not objc's.

This is because of how the xctest executable (the one that actually runs the unit tests, located at $XCODE_DIR/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest loads its bundle.

Pre-Xcode 7, it loaded all referenced test bundles before running any tests. This can be seen (for those that care), by disassembling the binary for Xcode 6.4, the relevant section can be seen for the symbol -[XCTestTool runTestFromBundle:].

In the Xcode 7 version of xctest, you can see that it delays loading of testing bundles until the actual test is run by XCTestSuite, in the actual XCTest framework, which can be seen in the symbol __XCTestMain, which is only invoked AFTER the test's host application is set-up.

Because the order of these being invoked internally changed, the way that your test's +load methods are invoked is different. There were no changes made to the objective-c-runtime's internals.

If you want to fix this in your application, you can do a few things. First, you could manually load your bundle using +[NSBundle bundleWithPath:], and invoking -load on that.

You could also link your test target back to your test host application (I hope you're using a separate test host than your main application!), which would make it be automatically loaded when xctest loads the host application.

I would not consider it a bug, it's just an implementation detail of XCTest.

Source: Just spend the last 3 days disassembling xctest for a completely unrelated reason.

这篇关于在 Xcode 7 中更改了 +load 方法顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆