iosobjective-cswiftappdelegateswizzling

AppDelegate Class not Available to Obj-C Embedded Library in iOS Swift App


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?


Solution

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