0

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.)

UIResponder+MyCustomMethods.m

/**
 *  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?

Blake Clough
  • 263
  • 2
  • 10
  • How does this even work in the Obj-c app? +load should be called before main, and main is where the UIApplication is created & initialized. – TomSwift Feb 10 '17 at 21:54

2 Answers2

3

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.

TomSwift
  • 39,369
  • 12
  • 121
  • 149
0

Apple Docs: +load Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

It looks like your AppDelegate isn't loaded yet. Try to call your swizzling from another place. E.g.

+(void)initialize{
[self swizzle...];

}

vaddieg
  • 626
  • 4
  • 7
  • I attempted this technique, but the initialize method is never called on the UIResponder class. I think because we're only using static methods there? Any way, all my code is never called when I use it. – Blake Clough Feb 13 '17 at 21:56