Skip to content
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