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
- Educational purposes.
- 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 calldidFinishLaunching...
).