Skip to content

iOS: Allow specifying your AppDelegate class's name in the Info.plist file. #1741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

60 changes: 53 additions & 7 deletions app/src/util_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

// Key used in Info.plist to specify a custom AppDelegate class name.
static NSString *const kFirebaseAppDelegateClassNameKey = @"FirebaseAppDelegateClassName";

#define MAX_PENDING_APP_DELEGATE_BLOCKS 8
#define MAX_SEEN_DELEGATE_CLASSES 32

Expand Down Expand Up @@ -163,6 +166,55 @@ @implementation UIApplication (FirebaseAppDelegateSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *appDelegateClassName = [[NSBundle mainBundle] objectForInfoDictionaryKey:kFirebaseAppDelegateClassNameKey];

if (appDelegateClassName && [appDelegateClassName isKindOfClass:[NSString class]] && appDelegateClassName.length > 0) {
Class specificClass = NSClassFromString(appDelegateClassName);
if (specificClass) {
NSLog(@"Firebase: Info.plist key '%@' found. Targeting AppDelegate class: %@. Swizzling of [UIApplication setDelegate:] will be skipped.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);

// Set this class as the sole "seen" delegate for Firebase processing.
// g_seen_delegate_classes_count should be 0 here in +load, but clear just in case.
for (int i = 0; i < g_seen_delegate_classes_count; i++) {
g_seen_delegate_classes[i] = nil;
}
g_seen_delegate_classes[0] = specificClass;
g_seen_delegate_classes_count = 1;
NSLog(@"Firebase: %@ is now the only delegate class Firebase will initially process.", appDelegateClassName);

// If there are already blocks pending (e.g., from other Firebase components' +load methods),
// execute them now for the specified delegate. These blocks will remain in the pending
// queue, mirroring the behavior of the original swizzled setDelegate: method which also
// does not clear pending blocks after execution (as they might apply to future delegates).
// In this Info.plist mode, however, Firebase won't process further setDelegate: calls.
if (g_pending_block_count > 0) {
NSLog(@"Firebase: +load (Info.plist Mode) - Executing %d PENDING block(s) for specified delegate: %@. (Blocks are not removed from queue).",
g_pending_block_count, NSStringFromClass(specificClass));
for (int i = 0; i < g_pending_block_count; i++) {
if (g_pending_app_delegate_blocks[i]) {
g_pending_app_delegate_blocks[i](specificClass);
}
}
NSLog(@"Firebase: +load (Info.plist Mode) - Pending blocks executed for specific delegate.");
}
// Skip swizzling. g_original_setDelegate_imp remains NULL.
return;
} else {
NSLog(@"Firebase Error: Info.plist key '%@' specifies class '%@', which was not found. Proceeding with default [UIApplication setDelegate:] swizzling.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);
}
} else {
if (appDelegateClassName) { // Key is present but value is invalid (e.g., empty string or wrong type).
NSLog(@"Firebase Warning: Info.plist key '%@' has an invalid value ('%@'). Proceeding with default [UIApplication setDelegate:] swizzling.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);
} else { // Key is not present.
// This is the default case, no special logging needed here beyond the swizzling log itself.
}
}

// Standard behavior: Swizzle [UIApplication setDelegate:]
NSLog(@"Firebase: Proceeding with swizzling of [UIApplication setDelegate:].");
Class uiApplicationClass = [UIApplication class];
SEL originalSelector = @selector(setDelegate:);
Method originalMethod = class_getInstanceMethod(uiApplicationClass, originalSelector);
Expand All @@ -172,18 +224,11 @@ + (void)load {
return;
}

// Replace the original method's implementation with Firebase_setDelegate
// and store the original IMP.
IMP previousImp = method_setImplementation(originalMethod, (IMP)Firebase_setDelegate);
if (previousImp) {
g_original_setDelegate_imp = previousImp;
NSLog(@"Firebase: Successfully swizzled [UIApplication setDelegate:] and stored original IMP.");
} else {
// This would be unusual - method_setImplementation replacing a NULL IMP,
// or method_setImplementation itself failed (though it doesn't typically return NULL on failure,
// it might return the new IMP or the old one depending on versions/runtime).
// More robustly, g_original_setDelegate_imp should be checked before use.
// For now, this logging indicates if previousImp was unexpectedly nil.
NSLog(@"Firebase Error: Swizzled [UIApplication setDelegate:], but original IMP was NULL (or method_setImplementation failed to return the previous IMP).");
}
});
Expand All @@ -194,6 +239,7 @@ + (void)load {
namespace firebase {
namespace util {

// This is the ORIGINAL implementation of RunOnAppDelegateClasses
void RunOnAppDelegateClasses(void (^block)(Class)) {
if (g_seen_delegate_classes_count > 0) {
NSLog(@"Firebase: RunOnAppDelegateClasses executing block for %d already seen delegate class(es).",
Expand Down
Loading