Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
417 views
in Technique[技术] by (71.8m points)

ios - Changed +load method order in Xcode 7

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...).
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...