I have a library written in Objective C, published as a Cocoapod. Part of what the library does is some swizzling of the AppDelegate class of any app that it is added to. When the library is added to a simple Objective-C app (a single-page app with nothing in it), the callback methods are swizzled properly and everyone is happy. However, when the library is added to a similar Swift app, the swizzling fails because the AppDelegate is null (or nil).
Here is a peek at the code within the library:
(Note: this method is called from the UIResponder's load: method.)
/**
* This class swizzles the methods needed to observe protocol methods
*/
@implementation UIResponder (WTAutomatics)
...
+ (void)swizzleAppDelegate:(SEL)original with:(SEL)replacement forProtocol:(Protocol *)protocol
{
id appDel = [[UIApplication sharedApplication] delegate];
Class appDelegate = [appDel class];
// Alternatively, we can do this. Both work the same.
// Class appDelegate = [[[UIApplication sharedApplication] delegate] class];
NSLog(@"Starting swizzle: %@", NSStringFromSelector(original));
if (!appDelegate) {
WTLog(@"Failed to swizzle because appDelegate is null.");
return;
}
if (class_conformsToProtocol(appDelegate, protocol)) {
// Do the method swizzling
...
}
}
In the code above, the value of appDelegate is valid and the swizzling works in any Objective C app. However, appDelegate is null when this is run within a Swift app and the swizzle fails.
Is there some difference in the order of initialization of the application delegate between the two languages? Is there something else I'm missing?
I'm not certain how this is working for you in the Obj-C application case. In my test, the +load is called before the application's main() method, which is where UIApplication is created and initialized. In my test, the following prints (null) for both lines:
@interface UIResponder (blah)
@end
@implementation UIResponder (blah)
+ (void) load
{
NSLog( @"application: %@", [UIApplication sharedApplication] );
NSLog( @"delegate: %@", [[UIApplication sharedApplication] delegate] );
}
@end
Here's a version that does what you want:
@interface UIResponder (blah)
@end
@implementation UIResponder (blah)
+ (void) load
{
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(didFinishLaunching:) name: UIApplicationDidFinishLaunchingNotification object: nil];
}
+ (void) didFinishLaunching: (NSNotification*) n
{
NSLog( @"application: %@", [UIApplication sharedApplication] );
NSLog( @"delegate: %@", [[UIApplication sharedApplication] delegate] );
}
@end
Furthermore, I don't think it is advisable to implement +load in a category extension because you don't know if that is overriding some +load method defined on the class itself. I believe when that is the case it is undefined which one is called? I'd have to check. A better solution might be to create your own UIResponder-derived class and put your +load shim there.